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
}
]
}
}
Related
- Taker API reference — message catalogue and error variants
- Taker quickstart — narrative walkthrough + raw sponsored-tx signing
- WS common conventions — units, envelopes, timeouts
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.