Skip to content

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 aspens

Or 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 code

Builder options:

MethodEffect
.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 quickstart

For 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_PRIVKEY belongs 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 an OrderAuthorization whose order_id must be computed with aspens::orders::derive_order_id — drift is silently fatal. (The legacy on-chain-lock authorization was removed.)

Resources