Acta Taker Wire Examples

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": null,
    "quote_mints": null
  }
}
{
  "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 → OrderAccepted → SponsoredTxToSign

{
  "type": "AcceptQuote",
  "data": {
    "rfq_id": "rfq-777",
    "maker": "MakerBOwnerPubkeyBase58",
    "order_id": "0x2222222222222222222222222222222222222222222222222222222222222222"
  }
}
{ "type": "OrderAccepted", "data": { "order_id": "0x2222...2222" } }
{
  "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 → OrderSubmitted → OrderConfirmed

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

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 → auto-reopen → re-accept

Under congestion the sponsored tx can miss its blockhash. The server retries internally up to 5×; if all fail it emits OrderFailed then RfqAvailableAgain:

{
  "type": "OrderFailed",
  "data": { "order_id": "0x2222...2222", "reason": "blockhash_expired", "order_version": 2 }
}
{
  "type": "RfqAvailableAgain",
  "data": {
    "rfq_id": "rfq-777",
    "rfq_version": 3,
    "reason": "tx_failed",
    "available_again_at": 1710000015
  }
}

Re-send AcceptQuote for the same (or a different still-valid) order_id to retry. 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.

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
      }
    ]
  }
}