Expand description
§acta-maker-sdk
Rust SDK for Acta’s Solana options RFQ venue. It supports makers that receive RFQs and send signed quotes, plus vault operators that quote, check served transactions, and run capital operations. Features are opt-in; the default set is empty.
§Install and credentials
Registration is not self-service. Acta must register the maker owner and its
quote-signing key on chain. Until then the connection fails with
maker_not_registered.
- The owner holds funds and signs maker transactions such as
DepositPremium,WithdrawPremium, andDepositFundsToPosition. - The registered quote-signing key authenticates and signs quotes. It can be the owner key.
Deposit a premium balance with DepositPremium before quoting. A fill debits its
premium from that balance.
Rust 1.89 or later is required.
[dependencies]
acta-maker-sdk = { version = "0.5.4", features = ["ws-client"] }For registration and environment setup, use the Rust maker guide.
§Quick start: managed maker
From this checkout, point the examples at a 64-byte Solana keypair JSON file:
export ACTA_KEYPAIR_PATH=/absolute/path/to/quote-signing-key.json
export ACTA_MAKER_OWNER=<owner base58>
cargo run --locked --features ws-client --example hello_auth
cargo run --locked --features ws-client,http --example managed_quoteACTA_WS_URL defaults to wss://devnet-api.acta.markets. DEVNET_WS, MAINNET_WS,
DEVNET_API, and MAINNET_API name the public origins.
The quote examples fetch GET /api/v1/venue and call .venue(&venue). Without
http, call .welcome(&welcome) after the session welcome. The examples send real
signed quotes at a fixed 1_000_000_000 price, so use a test maker on devnet.
They do not price, hedge, or fund.
The managed quote loop connects, authenticates, recovers state, subscribes to RFQs, signs a quote, and sends it in the recovered epoch:
use acta_maker_sdk::*;
use acta_maker_sdk::ws::{maker::MakerQuoteClient, managed::*, types::*};
use std::{sync::Arc, time::Duration};
static NONCES: AtomicNonceGenerator = AtomicNonceGenerator::new();
let signer = Arc::new(BytesSigner::from_keypair(&keypair)?);
let maker_owner = decode_base58_32(&owner)?;
let config = ManagedWsConfig::new(
DEVNET_WS,
HelloData {
protocol_version: WS_PROTOCOL_VERSION.to_string(),
features: vec![],
client_name: Some("my-bot".to_string()),
client_version: None,
},
signer.clone(),
owner,
)
.low_latency()
.with_initial_subscribe(SubscribeData {
request_id: uuid::Uuid::new_v4(),
channels: vec![WsChannel::Rfqs],
underlying_mints: None,
quote_mints: None,
});
let client = MakerQuoteClient::spawn(config)?;
let mut messages = client.subscribe_messages();
let epoch = client.wait_until_ready().await?;
while let Ok(msg) = messages.recv().await {
match msg.message() {
ServerMessage::RfqBroadcast(rfq) => {
let min_valid_until = QuoteExpiry::after(min_validity).ok_or("clock")?;
let valid_until =
QuoteExpiry::after(min_validity + Duration::from_secs(60)).ok_or("clock")?;
let quote = match RfqBinding::from_broadcast(rfq)?
.quote()
.maker_owner(maker_owner)
.price(Price::new(1_000_000_000))
.valid_until(valid_until)
.min_valid_until(min_valid_until)
.nonce(Nonce::new(NONCES.next_u64()?))
.sign(signer.as_ref())
{
Ok(quote) => quote,
Err(QuoteBuildError::ValidUntilAfterMarketExpiry { .. }) => continue,
Err(error) => return Err(error.into()),
};
client.quote_in_epoch(quote, epoch).await?;
}
ServerMessage::QuoteAcknowledged(ack) => println!("accepted {:?}", ack.order_id),
ServerMessage::QuoteOutbid(outbid) => println!("outbid on {}", outbid.rfq_id),
ServerMessage::QuoteRejected(rejected) => println!("rejected {:?}", rejected.reason),
ServerMessage::QuoteFilled(fill) => {
println!("filled on {}, position {}", fill.market_pda, fill.position_pda)
}
ServerMessage::RfqClosed(closed) => println!("closed {:?}", closed.reason),
_ => {}
}
}This sketch quotes only in its first epoch. managed_quote waits for each new
epoch and applies recovery before it quotes again. A socket write is not an
accepted quote; never replay an old signed quote after a reconnect.
QuoteFilled means the position opened on chain. Fund its settlement leg with
DepositFundsToPosition (or fund_position) if required. Acta’s keeper settles an
in-the-money funded position to the owner’s ATA; no SDK settlement call is needed.
§Features and examples
| Feature | Enables |
|---|---|
ws-client | MakerQuoteClient, MakerDataClient, ManagedWsHandle, and raw Tokio WsClient |
vault | VaultOperatorClient, checked vault transaction signing, and projection feed; includes ws-client and chain |
chain | Builders, derived addresses, checked account decoders, VaultManager, price routes, and served-transaction verification |
chain-rpc | ChainClient over Solana RPC; extends chain |
http | Read-only HttpClient using reqwest 0.13 and rustls with the platform verifier |
decimal | Exact rust_decimal views for Price, Strike, and Quantity |
test-helpers | ManagedWsHandle::test_handle and message injection |
| Example | Features | Purpose |
|---|---|---|
hello_auth | ws-client | Authenticate, print success, close |
managed_quote | ws-client, http | Recover state and send epoch-bound quotes |
quote | ws-client, http | Raw /maker handshake and signed quote |
discovery | ws-client | Read quotes, positions, markets, and summary on /maker/data |
indicative | ws-client | Answer unsigned indicative-price requests |
loopback_latency | ws-client | Local managed-client latency sample |
vault_operator | vault, http | Vault session, checked fill and swap signing |
vault_capital | chain-rpc | Capital-cycle crank |
curator_pt | chain-rpc, http | PT route planning and simulation |
Maker examples read ACTA_KEYPAIR_PATH, ACTA_MAKER_OWNER, and ACTA_WS_URL.
vault_operator also needs ACTA_VAULT_PDA. vault_capital reads
ACTA_KEYPAIR_PATH, ACTA_VAULT_PDA, and ACTA_RPC_URL (devnet by default).
ACTA_VAULT_PDA=<vault> cargo run --locked --features vault,http --example vault_operator
ACTA_VAULT_PDA=<vault> ACTA_RPC_URL=<rpc> \
cargo run --locked --features chain-rpc --example vault_capital§Maker sessions and recovery
Use MakerQuoteClient on /maker for quotes, replacements, cancellations, and
subscriptions. Use MakerDataClient on /maker/data for private reads. The data
endpoint supplies GetActiveRfqs, GetMyQuotes, GetOrderStatus, GetMakerPositions,
GetMarketsForMaker, GetMyTrades, and GetMmSummary.
MakerDataClient also receives the owner’s PositionUpdated, TradeExecuted, and
MakerCapsUpdated pushes after summary recovery. No subscription is needed for
those pushes. all_maker_positions(filter, timeout) pages GetMakerPositions when
MmSummary.positions_has_more is set.
use acta_maker_sdk::*;
use acta_maker_sdk::ws::{managed::*, maker::MakerDataClient, types::*};
use std::sync::Arc;
let config = ManagedWsConfig::new("wss://devnet-api.acta.markets", hello, signer, owner)
.with_endpoint(MakerWsEndpoint::Data);
let data = MakerDataClient::spawn(config)?;
data.wait_until_ready().await?;
let summary = data
.request(
GetMmSummaryMessage { request_id: uuid::Uuid::new_v4() },
std::time::Duration::from_secs(2),
)
.await?;Keep a data session for monitoring, funding, or hot standby. On restart, use
maker_open_positions(owner) to recover open and funded positions from chain.
Open /maker only when taking over: a second quote session replaces the first and
may cancel its quotes if cancel-on-disconnect is enabled. Data pushes are not
replayed, so reconcile after a gap or reconnect. Up to four data sessions may
share one owner; a fifth gets session_limit_exceeded.
Ready means required SDK recovery reads arrived. It does not mean a strategy
applied them, and recovery is not one atomic snapshot. Apply recovery and
lifecycle messages before trading. Commands are tied to the current epoch;
quote_in_epoch, replace_in_epoch, and batch_quotes_in_epoch return NotReady once
the session advances.
Subscriptions survive resume and fresh authentication. The first local receiver gets a bounded since-spawn backlog, so subscribe before waiting for traffic. See the maker API and WS conventions for delivery, close codes, subscriptions, rate limits, and error variants.
§Queries, sends, and cancellation
GetMyQuotes with Live returns the complete current owner set without pagination.
History is persisted and paged. GetOrderStatus returns Pending, Confirmed with a
position address, or Unknown. An empty Live set, timeout, or Unknown does not
prove an interrupted order has no exposure.
send confirms a socket write. send_await waits for a correlated response, and
try_send returns a SendTicket whose drop before dequeue cancels that send. Quote,
replacement, batch, and cancellation messages share a FIFO lane; a full lane
returns QueueFull. CancelQuoteAck and CancelAllQuotesAck name the IDs actually
cancelled; a QuoteCancelled lifecycle event is not a cancellation receipt.
§Quote outcomes
Subscribe before sending. Create QuoteOutcomeTracker::new(rfq_id, order_id, epoch, deadline).
Pass each ManagedInbound to observe, and call check with the
current epoch and monotonic time on transitions and at the deadline.
outcome reports Pending, Acked, Selected, terminal rejection, fill, expiry,
cancellation, loss, or Unknown. A selected order can remain unresolved after
RfqClosed until matching status proves a loss. A stream gap, closure, epoch
change, or deadline makes an unresolved order Unknown, never a proven failure.
Reconcile it through GetMyQuotes or GetOrderStatus; do not reuse its order ID.
§Quote construction and numeric units
RfqBinding::from_broadcast validates the market PDA, mints, expiry, and option
type under ACTA_PROGRAM_ID before signing. For another deployment, use
from_broadcast_for_program with a locally configured program ID. Never let an
RFQ choose the program ID.
QuoteBuilder requires maker_owner, price, validity bounds, and nonce. Read the
minimum validity and settlement buffer from HttpClient::venue or WelcomeData;
the builder rejects an impossible local validity window. Quote expiry is whole
Unix seconds. The full signing, validation, and replacement rules are in the
Rust maker guide and
maker API.
Price and Strike use the 1e9 PRICE_SCALE per underlying token, independent of
mint decimals. Quantity is underlying atoms. Without decimal, use
Price::as_decimal, Strike::as_decimal, Quantity::to_units, and
total_premium_quote_atoms. With decimal, use Price::to_decimal,
Strike::to_decimal, Price::from_decimal_floor, Quantity::to_decimal, and
Quantity::from_decimal_floor. The floor conversions return None for negative or
out-of-range values.
§Chain helpers and funding batches
The chain feature provides offline builders and checked account decoders.
With chain-rpc, maker_snapshot reads requested positions, premium ATAs, and
unique markets in batches of at most 100 keys. Missing positions and a missing
premium ATA are observations, not proof of closure or a failed RPC read.
Use maker_open_positions(owner) at startup or manual recovery, not in a tick
loop. PositionSnapshot::fund_instructions and required_settlement are pure.
For native settlement legs, prepend wsol::build_wrap_sol_ixs and aggregate the
deficit across the batch; budget the wallet balance, rent, and fees.
NativeSolFunding::with_default_reserve retains the single-position convenience.
deposit_premium, withdraw_premium, fund_position, and
send_instructions_with_options succeed only after confirmation at the client’s
commitment. send_instructions_with_blockhash is submit-only and accepts one
shared blockhash for a batch.
With chain-rpc plus ws-client, call confirm_signatures once for the batch. It
uses signatureSubscribe, then one chunked status read for unresolved signatures
on a deadline or WebSocket failure. signature_statuses is available with
chain-rpc alone. ConfirmationStatus::Pending is unknown, not a reason to send a
new payment.
§Capital-cycle reads
For a vault crank, ChainClient::unfunded_positions reads held positions that
still need their settlement leg. pending_capital_requests reads queued deposits
and withdrawals from program accounts. VaultManager builds the capital-cycle
instructions, and fund_maker_position moves the required leg into a position the
vault holds. These are chain observations and builders; select a cadence and
authority policy in the operator.
§Vault operators
VaultOperatorClient authenticates an operator for one vault. The connection grant
is current-connection state; it disappears on disconnect or invalidation. Check
permissions and Ready before trading. The vault operator
guide owns the
role, lifecycle, funding, and protocol detail.
verify_and_sign_sponsored_tx and verify_and_sign_swap rebuild the transaction
from the operator’s intent and sign only an exact match. Supply the vault account
read before the request, bounded compute budget, lookup tables, trade terms or
PairPriceRoutes, accepted staging routes, oracle shards, and an independent
minimum-output floor. verify_vault_open and verify_vault_swap expose the same
checks outside the client.
Checked signing verifies the fee payer, required signers, program IDs, account
privileges, instruction data, ATA setup, quote signature, price section, and
allowed swap hops. It checks deadlines but not the blockhash. A received ACK
only proves enqueueing; use swap_status or SwapSettled and refresh the vault for
execution state.
VaultProjectionAdvanced is a versioned projection cursor, not an execution
result. Ignore duplicates and older versions, then reload HTTP vault detail on a
newer version or a gap. VaultProjection::follows checks adjacency.
For a staged swap, request the same swap again only after the stage confirms.
The unstage must spend the whole staged amount at the open generation. A recovery
stage has its own VaultSwapKind::RecoveryStage shape; the checked signer rebuilds
it before signing.
§Price routes, venues, and staging
PriceRoute is the on-chain valuation source for a mint. For a served swap,
PairPriceRoutes supplies the vault’s main and second routes, accepted staging
routes, and permitted push-oracle shards. The deployment’s venue registry
controls the allowed hop programs. Read the detailed route, brick, and
checked-signing requirements in the vault operator
guide.
For direct PT routing with chain-rpc and http, call curator::Config::discover
once, then use Curator::plan and curator::execute. Re-discover after a route
dependency or second-leg roll. A staged plan must refresh the slot and re-quote
before its unstage; Plan::Staged::unstage is a fit preview, not a transaction to
cache and send.
The curator_pt example simulates only; it never broadcasts a mainnet transaction:
RPC_URL=... JUPITER_API_KEY=... CURATOR_KEYPAIR=/path/to/keypair.json \
VAULT=... AMOUNT_ATOMS=1000000 \
cargo +stable run --example curator_pt --features chain-rpc,http -- buyUse sell for PT input before maturity or redeem after maturity. A staged simulation covers only the first half; the second needs confirmed staging state.
§HTTP, wire, and upgrades
HttpClient reads venue limits, tokens, markets, vaults, and vault positions.
Venue, token, and market responses use ETag revalidation; vault reads are not
cached. Read current limits from the service rather than hardcoding them.
Wire enum values are extensible. Match error codes, not messages, and include fallback arms for non_exhaustive enums. *_unix_ms fields are milliseconds; signed quote expiry is Unix seconds. Chain events are identified by (signature, instruction_index).
Use WS_PROTOCOL_VERSION instead of hardcoding the protocol version.
Version 0.5.4 adds WelcomeData validity-window fields, the decimal feature,
maker-data owner pushes, QuoteOutcomeTracker, batch confirmation, PriceRoute
swaps, and staged recovery. See CHANGELOG.md for exact 0.5.4
and earlier migration notes, especially explicit maker owners and epoch-bound
quote methods.
§Latency sample
Release loopback sample with low_latency on one Apple silicon machine:
| Leg | p50 | p90 | p99 |
|---|---|---|---|
try_send to peer frame | 20 µs | 27 µs | 60 µs |
| peer send to session parse | 150 µs | 281 µs | 424 µs |
| session task to subscriber recv | 19 µs | 39 µs | 70 µs |
This measures SDK overhead only; network and server time are additional.
§Documentation
§License
MIT
Re-exports§
pub use error::ActaSdkError;pub use network::DEVNET_API;pub use network::DEVNET_WS;pub use network::MAINNET_API;pub use network::MAINNET_WS;pub use quote::QuoteBuildError;pub use quote::QuoteBuilder;pub use quote::RfqBinding;pub use types::PRICE_SCALE;pub use types::QuoteExpiry;pub use types::ids::Balance;pub use types::ids::ChainId;pub use types::ids::Decimals;pub use types::ids::DurationSeconds;pub use types::ids::MarketId;pub use types::ids::Nonce;pub use types::ids::OrderId;pub use types::ids::OrderVersion;pub use types::ids::PositionType;pub use types::ids::PositionTypeParseError;pub use types::ids::Price;pub use types::ids::Quantity;pub use types::ids::QuoteCount;pub use types::ids::RfqVersion;pub use types::ids::Slot;pub use types::ids::Strike;pub use types::ids::TimeoutSeconds;pub use types::ids::TradeCount;pub use types::ids::UserId;pub use types::ids::Volume;pub use types::invite::REFERRAL_CODE_MAX_LEN;pub use types::invite::REFERRAL_CODE_MIN_LEN;pub use types::invite::RESERVED_CODES;pub use types::invite::ReferralCode;pub use types::invite::ReferralCodeFormatError;pub use types::invite::TakerStatus;pub use types::invite::is_reserved;pub use types::vault::VaultDelegatePermissions;pub use types::vault::VaultLeg;pub use types::vault::VaultOperatorKind;pub use types::vault::VaultStagingStep;pub use types::vault::VaultSwapDirection;pub use types::vault::VaultSwapMode;pub use ws::client::PreparedClientMessage;pub use ws::client::ServerClose;pub use ws::client::WsClient;pub use ws::client::WsTransportConfig;pub use ws::error::WsClientError;pub use ws::error::WsResult;pub use ws::error::WsTransportConfigError;pub use ws::maker::MakerClientError;pub use ws::maker::MakerDataClient;pub use ws::maker::MakerDataRequest;pub use ws::maker::MakerDataRequestError;pub use ws::maker::MakerQuoteClient;pub use ws::maker::MakerResponse;pub use ws::managed::GapPolicy;pub use ws::managed::MakerWsEndpoint;pub use ws::managed::ManagedInbound;pub use ws::managed::ManagedMessageReceiver;pub use ws::managed::ManagedReceiveError;pub use ws::managed::ManagedWsConfig;pub use ws::managed::ManagedWsConfigError;pub use ws::managed::ManagedWsError;pub use ws::managed::ManagedWsEvent;pub use ws::managed::ManagedWsHandle;pub use ws::managed::ManagedWsState;pub use ws::managed::ManagedWsTerminationReason;pub use ws::managed::OutboundMessageError;pub use ws::managed::SendAwaitError;pub use ws::managed::SendTicket;pub use ws::managed::WaitUntilReadyError;pub use ws::managed::normalize_maker_data_ws_url;pub use ws::managed::normalize_maker_ws_url;pub use ws::managed::normalize_maker_ws_url_for_endpoint;pub use ws::managed::spawn_managed_ws;pub use ws::market_decimals::MarketDecimals;pub use ws::market_decimals::TokenDecimals;pub use ws::types::ClientMessage;pub use ws::types::ServerMessage;pub use ws::vault::FleetListing;pub use ws::vault::VaultFleet;pub use ws::vault::VaultFleetConfig;pub use ws::vault::SaleDesk;pub use ws::vault::SaleError;pub use ws::vault::SaleOutcome;pub use ws::vault::SaleStage;pub use ws::vault::SignedSale;pub use ws::vault::VaultSale;pub use ws::vault::SendGate;pub use ws::vault::Ungated;pub use ws::vault::VaultClientError;pub use ws::vault::VaultOperatorClient;pub use ws::vault::VaultOperatorConfig;pub use ws::vault::VaultProjection;pub use ws::vault::VaultProjectionReceiver;pub use ws::vault::VaultRequestError;pub use ws::vault::VaultTxError;pub use nonce::AtomicNonceGenerator;pub use nonce::NonceError;pub use nonce::NonceGenerator;pub use orders::BytesSigner;pub use orders::ORDER_DOMAIN_TAG;pub use orders::ORDER_ID_LEN;pub use orders::ORDER_PREIMAGE_LEN;pub use orders::OrderError;pub use orders::OrderPreimageArgs;pub use orders::SignerLike;pub use orders::build_order_preimage;pub use orders::compute_order_id;pub use orders::hash_order_preimage;pub use orders::order_id_hex;pub use orders::order_preimage_hex;pub use orders::sign_order_id_base58;pub use orders::sign_order_id_bytes;pub use orders::sign_order_id_from_base58_keypair;pub use orders::sign_order_id_with_async_signer;pub use orders::sign_order_id_with_signer;pub use orders::sign_order_id_with_signer_base58;pub use orders::verify_order_id_signature_base58;pub use orders::verify_order_id_signature_bytes;pub use signing::AsyncSignerLike;pub use signing::SigningError;pub use signing::SigningFuture;pub use wire::WireError;pub use wire::decode_base58_32;pub use wire::decode_base58_64;pub use wire::decode_hex_32;pub use wire::encode_base58;pub use wire::encode_hex;
Modules§
- chain
- error
- http
- Read-only client for the public Acta HTTP API.
- network
- Public Acta API origins. Append the WebSocket path (
/maker,/maker/data,/vault) or pass the origin tonormalize_maker_ws_url. - nonce
- orders
- quote
- Quote assembly:
RfqBindingdecodes identities and resolves offered strikes;QuoteBuilderderives the signed preimage andQuoteMessagefrom the same strike, price, expiry and nonce. - signing
- types
- Domain values shared by WebSocket and chain APIs.
- wire
- ws