Acta Taker Wire Examples

Payloads illustrate wire shapes. Replace abbreviated IDs, addresses, signatures and past timestamps with real values; request/session/RFQ IDs must be UUIDs and order IDs must decode to 32 bytes. These examples are not transactions to send unchanged.

Concrete JSON for a full taker session and the common branch scenarios. Message shapes and field semantics are in Taker API reference; the narrative walkthrough is in Taker quickstart.

All pubkeys/mints are base58, order_id is 64-char hex (optional 0x), amounts are u64 (price/strike 1e9-scaled, quantity in underlying atomic units), timestamps are Unix seconds unless the field name ends in _ms. Placeholder strings like MarketPdaBase58 stand in for real base58 values.

Complete Session (happy path)

1) Hello — client → server

{
  "type": "Hello",
  "data": {
    "protocol_version": "1.0.0",
    "features": [],
    "client_name": "my-app",
    "client_version": "0.1.0"
  }
}

2) Welcome — server → client

{
  "type": "Welcome",
  "data": {
    "protocol_version": "1.0.0",
    "server_version": "0.1.0",
    "min_supported_version": "1.0.0",
    "enabled_features": [],
    "server_time_unix_ms": 1710000000000
  }
}

3) StartAuth → AuthRequest → AuthChallenge → AuthSuccess

Taker auth is lazy; you may defer it until the first authenticated action. Start the fresh-sign flow with your wallet pubkey:

{ "type": "StartAuth", "data": { "pubkey": "TakerWalletPubkeyBase58" } }

The server replies with a challenge. The taker challenge includes a Wallet: line (the maker challenge does not):

{
  "type": "AuthRequest",
  "data": {
    "challenge": "Acta RFQ Authentication\n\nSign this message to authenticate your wallet.\n\nWallet: TakerWalletPubkeyBase58\nNonce: a1b2c3d4e5f6...hex64\nIssued At: 2024-03-09T12:00:00Z"
  }
}

Sign the raw UTF-8 bytes of challenge with your wallet key, base58-encode the 64-byte signature, and echo it back:

{
  "type": "AuthChallenge",
  "data": {
    "challenge": "Acta RFQ Authentication\n\nSign this message to authenticate your wallet.\n\nWallet: TakerWalletPubkeyBase58\nNonce: a1b2c3d4e5f6...hex64\nIssued At: 2024-03-09T12:00:00Z",
    "signature": "3q7uQqYc3...base58sig",
    "pubkey": "TakerWalletPubkeyBase58"
  }
}

The signature is verified directly against pubkey. On success:

{
  "type": "AuthSuccess",
  "data": {
    "session_id": "sess-abc-123",
    "expires_at": 1710086400
  }
}

For takers, expires_at is always a number. Persist session_id + expires_at for ResumeAuth.

4) Snapshot — server → client

Sent automatically after AuthSuccess. markets is the compact MarketInfo shape — fetch full descriptors separately (step 5) before creating an RFQ:

{
  "type": "Snapshot",
  "data": {
    "markets": [
      {
        "pda": "MarketPdaBase58",
        "underlying": "So11111111111111111111111111111111111111112",
        "quote": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
        "expiry_ts": 1710600000,
        "is_put": false
      }
    ]
  }
}

5) GetMarketDescriptors → MarketDescriptors

Full descriptors carry size_rule, decimals, and oracle PDAs needed to validate quantity and render prices. Call this before RfqRequest.

{ "type": "GetMarketDescriptors", "data": { "request_id": "req-md-1", "active_only": true } }
{
  "type": "MarketDescriptors",
  "data": {
    "request_id": "req-md-1",
    "markets": [
      {
        "market": {
          "chain_id": 0,
          "program_id": "33Ezs5eoa16QyPW8wifnyz2nCyMEcq2crqkVBNjnTE8U",
          "market_pda": "MarketPdaBase58",
          "underlying_mint": "So11111111111111111111111111111111111111112",
          "quote_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          "expiry_ts": 1710600000,
          "is_put": false,
          "collateral_mint": "So11111111111111111111111111111111111111112",
          "settlement_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
        },
        "underlying_oracle_pda": "UnderlyingOraclePdaBase58",
        "quote_oracle_pda": "QuoteOraclePdaBase58",
        "underlying_decimals": 9,
        "quote_decimals": 6,
        "size_rule": { "min_size": 1000000000, "max_size": 100000000000, "step": 1000000000 },
        "underlying_symbol": "SOL",
        "quote_symbol": "USDC"
      }
    ]
  }
}

6) Subscribe → SubscribeAck

request_id is required for takers. A taker subscribes to chain_events (position settled/liquidated), and optionally trades / stats / markets. rfqs and positions are maker channels — a taker's own RFQ/quote/order events arrive session-direct, and PositionUpdated is never delivered to takers.

{
  "type": "Subscribe",
  "data": {
    "request_id": "req-sub-1",
    "channels": ["chain_events", "trades"],
    "underlying_mints": [],
    "quote_mints": []
  }
}
{
  "type": "SubscribeAck",
  "data": { "request_id": "req-sub-1", "subscribed": ["chain_events", "trades"] }
}

7) RfqRequest → RfqCreated

quantity is underlying atomic units (also for cash-secured puts). Here: 5 SOL, covered call, strike 136.0.

{
  "type": "RfqRequest",
  "data": {
    "market": "MarketPdaBase58",
    "position_type": "covered_call",
    "strike": 136000000000,
    "quantity": 5000000000,
    "timeout_seconds": 30,
    "client_request_id": "cli-req-42"
  }
}
{
  "type": "RfqCreated",
  "data": {
    "rfq_id": "rfq-777",
    "rfq_version": 1,
    "client_request_id": "cli-req-42",
    "expires_at": 1710000030,
    "created_at": 1710000000,
    "order_options": [{ "strike": 136000000000 }]
  }
}

8) QuoteReceived (streamed, 0..N) — server → client

Each quote is firm and hash-bound via order_id. Two makers respond:

{
  "type": "QuoteReceived",
  "data": {
    "rfq_id": "rfq-777",
    "strike": 136000000000,
    "maker": "MakerAOwnerPubkeyBase58",
    "price": 50000000,
    "net_price": 49750000,
    "valid_until": 1710000330,
    "nonce": 42,
    "order_id": "0x1111111111111111111111111111111111111111111111111111111111111111"
  }
}
{
  "type": "QuoteReceived",
  "data": {
    "rfq_id": "rfq-777",
    "strike": 136000000000,
    "maker": "MakerBOwnerPubkeyBase58",
    "price": 51000000,
    "net_price": 50745000,
    "valid_until": 1710000335,
    "nonce": 7,
    "order_id": "0x2222222222222222222222222222222222222222222222222222222222222222"
  }
}

You pick the winner by order_id (here maker B, the higher premium). Show net_price; use price + order_id for the accept.

9) AcceptQuote → SponsoredTxToSign

{
  "type": "AcceptQuote",
  "data": {
    "rfq_id": "rfq-777",
    "maker": "MakerBOwnerPubkeyBase58",
    "order_id": "0x2222222222222222222222222222222222222222222222222222222222222222"
  }
}
{
  "type": "SponsoredTxToSign",
  "data": {
    "order_id": "0x2222...2222",
    "tx_base64": "AQABAg... (base64 v0 VersionedTransaction) ...",
    "signature_deadline": 1710000030
  }
}

Sign tx_base64 and return it before signature_deadline. The taker fills signature slot 1 (slot 0 is the keeper fee-payer); see Sponsored transaction: raw signing for the byte-level layout.

10) SubmitSignedSponsoredTx → OrderAccepted → OrderSubmitted → OrderConfirmed

{
  "type": "SubmitSignedSponsoredTx",
  "data": {
    "order_id": "0x2222...2222",
    "tx_base64": "AQABAg... (same tx, taker slot now filled) ..."
  }
}
{ "type": "OrderAccepted", "data": { "order_id": "0x2222...2222", "order_version": 1 } }
{
  "type": "OrderSubmitted",
  "data": {
    "order_id": "0x2222...2222",
    "tx_signature": "5eyk...base58sig",
    "order_version": 2
  }
}
{
  "type": "OrderConfirmed",
  "data": {
    "order_id": "0x2222...2222",
    "position_pda": "PositionPdaBase58",
    "order_version": 4
  }
}

tx_signature arrives on OrderSubmitted (not repeated on OrderConfirmed).

11) RfqClosed — server → client

Terminal RFQ event; follows OrderConfirmed on a fill. Drop per-RFQ state here.

{
  "type": "RfqClosed",
  "data": {
    "rfq_id": "rfq-777",
    "rfq_version": 2,
    "reason": "filled",
    "winner": {
      "maker": "MakerBOwnerPubkeyBase58",
      "price": 51000000,
      "tx_signature": "5eyk...base58sig"
    },
    "closed_at": 1710000012
  }
}

Additional scenarios

Session resume (skip the wallet signature)

{ "type": "ResumeAuth", "data": { "session_id": "sess-abc-123" } }

Valid session → AuthSuccess (as in step 3, no AuthRequest/signing). Invalid/expired → AuthError:

{ "type": "AuthError", "data": { "reason": "session_expired" } }

Cancel an RFQ

{ "type": "CancelRfq", "data": { "rfq_id": "rfq-777", "request_id": "req-cancel-1" } }
{
  "type": "RfqClosed",
  "data": { "rfq_id": "rfq-777", "rfq_version": 2, "reason": "taker_cancelled", "closed_at": 1710000020 }
}

Blockhash expiry → reopen and reconcile

Keeper may retry retryable submission failures within five attempts. Exhaustion does not guarantee either message below. An uncertain keeper failure leaves Core Enqueued; reconcile with GetOrderStatus. A wire failure observation can look like:

{
  "type": "OrderFailed",
  "data": { "order_id": "0x2222...2222", "reason": "blockhash_expired", "order_version": 3 }
}

Only a locally proven-unforwarded failure may reopen an eligible RFQ with reason: "tx_failed":

{
  "type": "RfqAvailableAgain",
  "data": {
    "rfq_id": "rfq-777",
    "rfq_version": 3,
    "reason": "tx_failed",
    "available_again_at": 1710000015
  }
}

Refresh the reopened RFQ and select a currently available quote. Rollback discards the winning quote; do not replay its old order_id. reason for a signature timeout is signature_timeout; for a tx-build failure, tx_build_failed. Non-retryable OrderFailed reasons: on_chain, submission_rejected, safety_timeout, shutdown — surface to the user, do not re-accept.

Reconcile by order_version: accepted is 1, submitted 2, failed/expired 3, and confirmed 4. A late on-chain confirmation may therefore replace a local failure; a later failure can never replace confirmation.

RFQ expired with no fill

{
  "type": "RfqClosed",
  "data": { "rfq_id": "rfq-778", "rfq_version": 1, "reason": "expired", "closed_at": 1710000060 }
}

Invite gating (closed mainnet only)

After AuthSuccess, an unregistered taker receives (unit variant, no data):

{ "type": "RequireInvite" }

Redeem before trading:

{ "type": "RedeemInvite", "data": { "request_id": "req-inv-1", "code": "abc123" } }
{
  "type": "InviteRedeemed",
  "data": { "request_id": "req-inv-1", "referral_code": "my-code" }
}

An RfqRequest before redemption fails with a typed InviteRequired error (delivered as RequestError when the request carried a request_id). Devnet is open — no invite needed.

Size-rule violation (RequestError)

quantity must satisfy min_size <= quantity <= max_size and (quantity - min_size) % step == 0:

{
  "type": "RequestError",
  "data": {
    "request_id": "req-rfq-bad",
    "error": { "type": "Generic", "data": { "code": "invalid_quantity_size_rule", "message": "quantity violates min/max/step" } }
  }
}

Reconcile after reconnect

Subscriptions and in-flight state are not replayed; re-auth, resubscribe, then query:

{ "type": "GetMyActiveRfqs", "data": { "request_id": "req-rec-1" } }
{ "type": "GetOrderStatus",  "data": { "request_id": "req-rec-2", "order_id": "0x2222...2222" } }
{ "type": "GetPositions",    "data": { "request_id": "req-rec-3" } }
{
  "type": "Positions",
  "data": {
    "request_id": "req-rec-3",
    "positions": [
      {
        "pda": "PositionPdaBase58",
        "market": "MarketPdaBase58",
        "underlying_mint": "So11111111111111111111111111111111111111112",
        "quote_mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
        "position_type": "covered_call",
        "status": "open",
        "strike": 136000000000,
        "quantity": 5000000000,
        "price": 51000000,
        "total_premium": 253725,
        "created_at": 1710000012,
        "expiry_ts": 1710600000
      }
    ]
  }
}

Recovery after reconnect

After AuthSuccess, reconcile GetMyActiveRfqs, GetPositions and GetOrderStatus. Only a successful resume of the same credential transfers ownership of an unfinished signature. For the same pending order whose signature was not sent, repeat the exact AcceptQuote to retrieve the signing payload. For a submitted/enqueued order, query status without replaying trading commands.

OrderStatus carries { request_id, order_id, state }, with state equal to { "type": "pending" }, { "type": "confirmed", "position_pda": "..." } or { "type": "unknown" }. It has no order_version; versions apply to lifecycle pushes. unknown, missing positions and timeouts leave the outcome unresolved. See Delivery & recovery.