Aspens SDK
A comprehensive SDK and CLI tools for interacting with an Aspens Markets Stack.
The core library is published on crates.io as aspens.
The aspens-cli, aspens-repl, and aspens-admin binaries live in this
workspace and are built from source.
Available Commands
| Command | Description |
|---|---|
config |
Fetch and display the configuration from the server |
deposit <network> <token> <amount> |
Deposit tokens to make them available for trading |
withdraw <network> <token> <amount> |
Withdraw tokens to a local wallet |
buy-market <market> <amount> |
Send a market BUY order (executes at best available price) |
buy-limit <market> <amount> <price> [--post-only] |
Send a limit BUY order (executes at specified price or better). With --post-only, the order is rejected if it would cross at submission — guarantees maker-side execution. |
sell-market <market> <amount> |
Send a market SELL order (executes at best available price) |
sell-limit <market> <amount> <price> [--post-only] |
Send a limit SELL order (executes at specified price or better). See --post-only above. |
cancel-order <market> <side> <order_id> |
Cancel an existing order by its ID |
stream-orderbook <market> [--historical] [--trader <addr>] |
Stream orderbook entries in real-time |
stream-trades <market> [--historical] [--trader <addr>] |
Stream executed trades in real-time |
balance |
Fetch the current balances for all supported tokens across all chains |
status |
Show current configuration and connection status |
trader-public-key |
Get the public key and address for the trader wallet |
signer-public-key [--chain-id <id>] |
Get the signer public key(s) for the trading instance |
All commands are available in both aspens-cli and aspens-repl.
Project Structure
This is a Cargo workspace with four main components:
aspens/- Core Rust library crate with trading logic and gRPC clientaspens-cli/- Command-line interface binary for scripted operationsaspens-repl/- Interactive REPL binary for manual tradingaspens-admin/- Administrative CLI for stack configuration (chains, tokens, markets)
Prerequisites
- Install Rust:
|
- Install Just (Optional but Recommended):
Just is a command runner that simplifies common development tasks.
- Configure environment:
# Edit .env with your configuration (ASPENS_MARKET_STACK_URL, TRADER_PRIVKEY, etc.)
Building
Usage
1. As a Rust Library
Install from crates.io:
Or add it manually to your Cargo.toml:
[]
= "0.4"
Full client (gRPC + trading commands + RPC submission):
use ;
async
Stateless signing only (no gRPC, no tokio, no RPC client — e.g. browser
via wasm-bindgen, edge workers, or a service that submits orders over
its own transport):
[]
= { = "0.4", = false, = ["evm", "solana"] }
use ;
use gasless_lock_signing_hash;
use ;
// Build the canonical order id from a few intent fields:
let order_id = derive_order_id;
// EVM: produce the EIP-712 digest a wallet must sign for a gasless lock.
let digest = gasless_lock_signing_hash?;
// Solana: produce the borsh payload for Ed25519 signing of a gasless open.
let msg = gasless_lock_signing_message?;
The pure modules:
aspens::orders— chain-agnosticderive_order_id,GaslessLockParams.aspens::evm— sol! bindings forMidribV2/IAllowanceTransfer/MidribDataTypes, EIP-712 domain consts, gasless-order builder and hasher, EIP-191 envelope signer.aspens::solana— PDA derivations, instruction builders, borsh payload encoder, Ed25519 precompile ix, well-known program ids.
2. Interactive Mode (REPL)
# Inside the REPL
3. Scripted Mode (CLI)
Post-only orders
Pass --post-only to buy-limit / sell-limit to guarantee your order
adds liquidity rather than taking it. If the price would cross the
opposing side of the book at submission, arborter returns
FAILED_PRECONDITION and does not lock funds on-chain — no gas is
spent and your gasless signature stays unused, so you can resubmit at a
different price.
# Post a maker-only bid at 100. If the best ask is ≤ 100, the order
# is rejected and you can retry at 99 (or below).
# Same on the sell side: rejected if there's a resting bid at ≥ 200.
In Rust:
use send_order;
let response = send_order_with_wallet.await?;
Post-only is incompatible with market orders (the SDK pre-rejects
post_only=true with price=None before signing) and with the
buy-marketable / sell-marketable CLI variants (which are designed
to cross — the CLI hard-codes post_only=false for them).
4. Admin CLI
# Initialize admin (first time only)
# Login to get JWT
# Admin commands (JWT set in .env or via --jwt flag)
Cargo Feature Flags
The aspens crate exposes three orthogonal feature groups, all
default-on. Consumers can trim down to just what they need:
| Feature | What it pulls in | When to keep / drop |
|---|---|---|
evm |
aspens::evm (sol! bindings, EIP-712 hasher, envelope signer) + aspens::orders. Tiny — alloy-primitives/alloy-sol-types/alloy-signer-local. |
Keep if you build or sign EVM orders. |
solana |
aspens::solana (PDA derivations, instruction builders, borsh payload encoder, Ed25519 precompile ix). Pulls solana-sdk, borsh, bs58, ed25519-dalek. |
Keep if you build or sign Solana orders. |
client |
Full runtime: AspensClient, trading commands, gRPC (tonic/prost), async runtime (tokio), RPC submission (solana-client, alloy-contract, alloy-provider). |
Keep for the CLI/REPL/admin experience or anything that talks to the Aspens stack. Drop it for browser / embedded / offline-signing. |
Common configurations:
- Default (everything):
aspens = "0.4" - Lean EVM signing:
aspens = { version = "0.4", default-features = false, features = ["evm"] } - Lean Solana signing:
aspens = { version = "0.4", default-features = false, features = ["solana"] } - Both chains, no client runtime:
aspens = { version = "0.4", default-features = false, features = ["evm", "solana"] }
The aspens-cli, aspens-repl, and aspens-admin binaries all depend
on the default feature set.
Just Commands
Architecture
Core Library (aspens/)
- AspensClient - Main client with builder pattern for configuration
- Trading operations - Deposit, withdraw, buy, sell, balance queries across EVM and Solana chains
- Curve-agnostic wallet -
Wallet::Evm(secp256k1) andWallet::Solana(Ed25519) behind one signing interface - Chain dispatch -
ChainClientroutes RPC calls to Alloy (EVM) orsolana-clientbased on chain architecture - Executor pattern - Async/sync execution strategies
- gRPC client - Protocol buffer communication with an Aspens Market Stack
- Client-side order helpers (
aspens::orders/aspens::evm/aspens::solana) — stateless builders for the gRPC order payload:derive_order_id, EIP-712 gasless-lock hasher (EVM), borshOpenForSignedPayloadencoder (Solana), PDA derivations, Ed25519 precompile ix. Available without theclientfeature for browser / embedded callers. - EVM integration - Midrib V2 ABI bindings (shared JSON artifacts with arborter), Alloy signer, Permit2
- Solana integration - Midrib Anchor program: Anchor discriminators, PDA seeds, SPL token flow
CLI Binary (aspens-cli/)
Command-line interface for scripted trading operations.
REPL Binary (aspens-repl/)
Interactive Read-Eval-Print Loop for manual trading with command history and session state.
Admin Binary (aspens-admin/)
Administrative CLI for managing stack configuration with EIP-712 signature authentication.
Decimal Handling
Aspens handles tokens with different decimal places across chains. The SDK works in "pair decimals" format internally. See decimals.md for detailed conversion examples.
Token Assumptions
Important: Aspens only supports tokens with standard ERC-20 / SPL semantics. Adding a non-compliant token to a market — via aspens-admin set-token or the admin-console — will produce incorrect balances, fee leakage, or stuck funds. The contracts do not detect non-compliant tokens; gating happens here, in market configuration.
A token is safe to add only if all of the following hold:
- Standard transfer semantics. A
transfer(to, amount)reduces the sender's balance by exactlyamount. No transfer hooks that re-enter or opportunistically revert. - No fee-on-transfer. Tokens that charge a fee on transfer (reflection tokens, deflationary tokens) silently shift cost onto the user's existing
tradeBalanceduring_depositAndLock, cause the Aspens vault to under-collect fees, and under-deliverSETTLE_AND_WITHDRAWpayouts. - No rebasing. Tokens whose balances change between two reads of
balanceOf(AMPL-style, aTokens in rebase mode) break thebalanceBefore/balanceAfterreconciliation used throughout the contract. Use the wrapped, non-rebasing variant (e.g. wstETH, not stETH). - No supply-pause that strands open orders. Pausable tokens are tolerable as long as pauses are short-lived; pauses of a duration longer than the cancel-and-unlock window can strand
lockedTradeBalanceuntil the pause lifts. - Blocklist tokens (USDC-style) are accepted with caveats. Funds remain correctly accounted for, but a blocklisted address cannot withdraw or settle out until removed from the list.
Quick checklist before running aspens-admin set-token:
- Read the token's
transferimplementation — confirm_balances[from] -= amountis the only debit. - Run a probe transfer (any amount) and check
balanceAfter == balanceBefore - amounton both sides. - Confirm
balanceOfis a pure function of stored state, not a function of total supply.
If any of these checks fails, do not add the token. Common safe examples: USDC, USDT (on chains where USDT does not enable fee-on-transfer), WBTC, WETH, DAI, most stablecoins.
Solana-specific notes
The on-chain midrib program uses the legacy SPL Token program, not Token-2022. Mints owned by the Token-2022 program (TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb) will fail deserialization at every entry point — by design, since Token-2022's transfer-fee, interest-bearing, and confidential-transfer extensions would all break the program's deposited += amount accounting.
For gasless open_for flows the user signs an OpenForSignedPayload with an args.deadline slot. Pick this tight — current_slot + 600 (~4 minutes at 400ms slots) is a sensible default. The on-chain UsedNonce tombstone guarantees a signed payload is single-use regardless of deadline, but a tight deadline limits the window between user-signs and arborter-submits.
Documentation
- Decimal Conversion Guide - Understanding decimal handling
- CHANGELOG.md - Release notes per version
- CLAUDE.md - Architecture guide for development
Versioning
The aspens crate follows Semantic Versioning. The
workspace is pre-1.0, so the conventions in effect today are:
- Patch releases (
0.4.x→0.4.y) — bug fixes, performance work, internal refactors. No source-breaking changes to public items inaspens::{client, wallet, orders, evm, solana, decimals}or to the re-exports at the crate root. - Minor releases (
0.4.x→0.5.0) — may include breaking changes to the public API surface (renames, signature changes, removals). Notable changes are recorded inCHANGELOG.md. - Internal modules —
aspens::grpc(and any module marked#[doc(hidden)]orpub(crate)) are implementation details and may change in any release. Generated proto bindings underaspens::proto::*andaspens::attestation::*track the upstreamprotos/repo and follow its compatibility, not the SDK's. - CLI / REPL / Admin binaries — version-bumped together with the
library. Flag and command renames are called out in
CHANGELOG.md.
When in doubt about whether a change is breaking, check the changelog entry for the target version.
License
This project is licensed under the Apache License 2.0. See the LICENSE file for details.