Developer Guide
Build trading applications against an Aspens Market Stack using the
aspens Rust SDK or directly over
gRPC. This guide focuses on the Rust path; for the wire contract, see
the API Reference.
1. Install
cargo add aspensOr pin a version explicitly:
[dependencies]
aspens = "0.4"
tokio = { version = "1", features = ["full"] }
eyre = "0.6"The crate is published on crates.io. Source lives at github.com/aspensprotocol/sdk.
For browser, embedded, or offline-signing consumers that do not need
the gRPC / RPC runtime, drop the client feature:
aspens = { version = "0.4", default-features = false, features = ["evm", "solana"] }This skips tonic / prost / tokio / solana-client and exposes only the
stateless signing modules — see Stateless signing.
2. Configure
# .env
ASPENS_MARKET_STACK_URL=http://localhost:50051
TRADER_PRIVKEY=<64-char hex, no 0x prefix> # EVM wallet
TRADER_PRIVKEY_SOLANA=<base58 keypair> # Solana wallet (only if you trade on Solana chains)AspensClient::builder() reads .env from the current directory by
default; override with .with_env_file(path).
3. Connect
use aspens::{AspensClient, DirectExecutor};
let client = AspensClient::builder()
.with_url("http://localhost:50051")? // overrides ASPENS_MARKET_STACK_URL
.build()?;
let executor = DirectExecutor; // or BlockingExecutor for REPL-style codeBuilder options:
| Method | Effect |
|---|---|
.with_url(url) | Override ASPENS_MARKET_STACK_URL |
.with_env_file(path) | Load env vars from a custom file instead of ./.env |
client.stack_url() returns the resolved URL; client.get_env(key)
reads the loaded environment.
4. Trade
The aspens::commands::trading::* modules are the public API. They
take a Wallet for curve-aware signing and an already-fetched config:
use aspens::{
load_trader_wallet_for_network, AspensClient, DirectExecutor,
};
use aspens::commands::config;
use aspens::commands::trading::{balance, deposit, send_order};
#[tokio::main]
async fn main() -> eyre::Result<()> {
let client = AspensClient::builder().build()?;
let executor = DirectExecutor;
let stack_url = client.stack_url().to_string();
// Fetch chains / tokens / markets from the stack.
let cfg = executor.execute(config::get_config(stack_url.clone()))?;
// Pick a wallet that matches the chain's curve.
let wallet = load_trader_wallet_for_network(&cfg, "base-sepolia")?;
// Deposit 1000 base-units of USDC on base-sepolia.
executor.execute(deposit::call_deposit_from_config_with_wallet(
"base-sepolia".into(), "USDC".into(), 1000, &wallet, cfg.clone(),
))?;
// Inspect balances across every chain you have a wallet for.
executor.execute(balance::balance_from_config_with_wallet(cfg.clone(), &wallet))?;
Ok(())
}The _with_wallet and _with_wallets variants are the canonical
signatures; the legacy privkey: String overloads still exist as
EVM-only thin wrappers and are kept for backwards compatibility.
Wallet is curve-agnostic:
use aspens::{load_trader_wallet, CurveType};
let evm = load_trader_wallet(CurveType::Secp256k1)?; // reads TRADER_PRIVKEY
let solana = load_trader_wallet(CurveType::Ed25519)?; // reads TRADER_PRIVKEY_SOLANA
let address = evm.address(); // hex (EVM) or base58 (Solana)
let signature = evm.sign_message(b"hello").await?;Cross-chain orders need both wallets. send_order_with_wallets(&[&evm, &solana], …)
picks the right one per chain based on the chain's architecture.
5. Stateless signing (no runtime)
Order entry is authenticated by a single envelope signature over
the encoded Order — there is no separate on-chain-lock signature. The
stateless helpers let a wallet UI, backend, or service produce the exact
bytes the arborter validates without pulling in gRPC or tokio:
use aspens::orders::derive_order_id;
// Canonical order id — carried in OrderAuthorization { order_id, amount_in }.
// Must match what the arborter recomputes.
let order_id = derive_order_id(
&user_pubkey_bytes, nonce, origin_chain, dest_chain,
&input_token_bytes, &output_token_bytes, amount_in, amount_out,
);EVM (feature evm):
use aspens::evm::sign_send_order_envelope;
// EIP-191 envelope over the encoded Order — the sole order-entry
// authorization; goes in SendOrderRequest.signature_hash.
let envelope_sig = sign_send_order_envelope(&signer, &encoded_order).await?;Solana (feature solana): the wallet Ed25519-signs the same encoded
Order for the envelope. aspens::solana also exposes the
deposit / withdraw-voucher helpers — ed25519_verify_ix,
withdrawal_voucher_signing_message, the PDA derivations (factory,
instance, user_balance, vault, ATA), and well-known program ids.
The EVM module additionally exposes RPC-enabled MidribV3 sol!
bindings (deposit / withdraw-voucher / balance calls) and the EIP-712
domain constants. All ported from the arborter's reference
implementation; the parity is locked down by
aspens/tests/client_parity.rs snapshots.
The legacy on-chain-lock signing helpers (
build_gasless_cross_chain_order,gasless_lock_signing_hash,gasless_lock_signing_message,GaslessLockParams,OpenOrderArgs) were removed with the on-chain order path. Order entry needs only the envelope signature above.
6. CLI / REPL surface
For scripted or interactive flows without writing Rust:
# Connection
aspens-cli --stack http://localhost:50051 status
aspens-cli config
# Trader info
aspens-cli trader-public-key
aspens-cli signer-public-key --chain-network base-sepolia
# Balances and transfers
aspens-cli balance
aspens-cli deposit base-sepolia USDC 1000
aspens-cli withdraw base-sepolia USDC 100
# Orders — cross-chain orders require a limit price.
# For "take the top of book", use the marketable helpers: they
# snapshot the resting book and submit a slippage-capped limit.
aspens-cli buy-limit USDC/USDT 100 0.99
aspens-cli sell-limit USDC/USDT 50 1.01
aspens-cli buy-marketable USDC/USDT 100 --slippage-bps 50
aspens-cli sell-marketable USDC/USDT 50 --slippage-bps 50
aspens-cli cancel-order USDC/USDT buy <order_id>
# Streams
aspens-cli stream-orderbook USDC/USDT --historical
aspens-cli stream-trades USDC/USDT --trader 0x...
# TEE attestation
aspens-cli get-attestation --report-data <hex>Use aspens-repl for an interactive session with a similar command
surface. aspens-admin covers stack configuration — see the
Operator Guide.
7. Testing
Point ASPENS_MARKET_STACK_URL at a testnet stack and run the same
flows. The SDK ships an end-to-end example:
git clone https://github.com/aspensprotocol/sdk
cd sdk
cargo run --example quickstartFor unit tests, build the client with with_url(...) to avoid loading
.env. The client itself is cheap to construct — there is no I/O
until you execute a command.
8. Security notes
- Never commit private keys.
TRADER_PRIVKEYbelongs in.env(which is gitignored) or a secrets vault. - Use distinct wallets for testnet and production.
- Order entry is authenticated by the EIP-191/Ed25519 envelope
signature over the encoded
Order; every order also carries anOrderAuthorizationwhoseorder_idmust be computed withaspens::orders::derive_order_id— drift is silently fatal. (The legacy on-chain-lock authorization was removed.)