Operator Guide
This guide walks through deploying and configuring an Aspens Market
Stack (AMS). Stack ops live in the
infra repository (Docker
Swarm + a justfile); per-stack configuration is driven by the
aspens-admin CLI from the
SDK repository.
1. Prerequisites
- Docker (with Swarm mode) on the host
- A domain name pointing at the host (for any non-local deploy)
- RPC endpoints for each chain you want to support
- A funded admin wallet (EVM key for EIP-712 login)
- An admin wallet funded with native gas on every chain you'll support
(the arborter signs net-settlement
settleBatchand withdrawal-voucher transactions from this account; trading itself is off-chain) - On Tron, TRX in the TEE signer's Tron account (see Tron chains) — Tron transactions pay for energy by burning TRX
2. Bring up the stack
The infra/ repo contains three stack flavors:
| Stack | Purpose |
|---|---|
local | Anvil / solana-test-validator on your laptop |
googlecloud | Confidential VM deploy behind your own domain |
testing-suite | Integration-test scaffolding |
Local development is just-driven:
git clone https://github.com/aspensprotocol/infra
cd infra
just local-up # bring up the local stack
just test-up evm-evm-local # deploy + configure a scenario end-to-endAvailable scenarios live under infra/scenarios/*.toml
(evm-evm-local, evm-evm-testnet-minimal, evm-evm-testnet,
solana-evm-local, evm-evm-solana-local, evm-evm-solana-testnet,
base-arb-op-solana-local).
For a CVM deploy, see infra/stacks/googlecloud/README.md — it covers
domain templating, traefik / certbot setup, secret rotation, and image
bumps.
3. Configure with aspens-admin
Install the admin CLI:
cargo install --locked --git https://github.com/aspensprotocol/sdk aspens-adminConfigure the environment:
# .env
ASPENS_MARKET_STACK_URL=http://localhost:50051
ADMIN_PRIVKEY=<64-char hex, no 0x prefix>Initialize the admin (fresh stack only)
aspens-admin init-admin --address 0xYourAdminAddressThis is a one-time call. On any subsequent stack, log in to obtain a JWT:
aspens-admin login # uses ADMIN_PRIVKEY for EIP-712 signatureSave the returned JWT to .env as ASPENS_JWT (or pass --jwt per
command).
Add chains
aspens-admin set-chain \
--architecture EVM \
--canonical-name "Base Sepolia" \
--network base-sepolia \
--chain-id 84532 \
--rpc-url https://your-base-sepolia-rpc \
--factory-address 0xYourMidribFactoryAddress \
--explorer-url https://sepolia.basescan.orgThe arborter mints the per-chain instance signer key inside the TEE
when you call set-chain — secp256k1 for --architecture EVM,
Ed25519 for --architecture Solana. For --architecture Tron the
signer reuses its EVM key (see Tron chains). The
architecture string is
normalized to lowercase server-side, so casing on the CLI doesn't
matter.
On a fresh chain registration the arborter derives the instance signer
address and returns it automatically. If you re-run set-chain for a
chain whose signer keys already exist (e.g. after a database reset),
that auto-derivation is skipped and the SDK later fails with
invalid instance_signer_address. Supply it explicitly with
--instance-signer-address in that case — read the value back with
aspens-cli signer-public-key --chain-network <network>.
Tron chains
Register a Tron chain with --architecture tron. The chain's RPC URL
is the node's HTTP base (for example a TronGrid endpoint); the arborter
derives the JSON-RPC and wallet paths from it. TronGrid's API key goes
in as a header, so add the endpoint with auth scheme Header and
header name TRON-PRO-API-KEY in the admin console's RPC endpoints
dialog. Unlike Solana endpoints, which
authenticate through the URL only, Tron endpoints may use header auth.
Addresses inside the venue are 20-byte 0x hex, exactly as on EVM. The
T... form is a display form only: convert it to hex before passing it
to aspens-admin or anything else that talks to the venue.
Funding the signer. The TEE signer's Tron account is the same
20-byte account as its EVM one, shown in T... form on Tron. It must
hold TRX, because Tron charges energy and the signer has none staked, so
it burns TRX to pay for settlements and fee sweeps. Withdrawals are
different: the user's own wallet sends and pays for the withdrawal
transaction, using a voucher the venue signs, so they cost the signer no
TRX.
- A settle of 50 deltas burned about 165 TRX on Nile with no staked energy.
- The arborter caps each settle at a fee limit of 477.246 TRX.
- The deployer's funding gate requires 4,772.46 TRX in the account before it will proceed.
A broadcast rejected for balance, or a transaction that runs out of energy, fails with an error naming the signer's address; top the account up with TRX.
Deploying the instance. The admin wallet cannot deploy a Tron instance, and the admin console's Deploy button is disabled for Tron chains. Deploy Tron instances with the deployer.
Add tokens
aspens-admin set-token \
--network base-sepolia \
--name "USD Coin" \
--symbol USDC \
--address 0xYourUsdcAddress \
--decimals 6Deploy trade contracts
aspens-admin deploy-contract base-sepolia --fees 100 # 100 bps = 1%For EVM chains, the admin's wallet signs and broadcasts
MidribFactory.createInstance. Tron instances are deployed with the
deployer, not with this command. For Solana chains, the arborter signs
and submits create_instance server-side using the factory's owner key
(only the factory owner can call it under the has_one = owner
constraint).
Fees (two tiers)
Fees are two-tier and taker-pays (charged on the received leg at settlement):
- Maintenance fee — the Aspens cut, baked into the instance at deploy
time via
deploy-contract --fees <bps>(immutable for that instance). - Operator fee — the stack operator's own cut, set (and re-set) after deploy:
aspens-admin set-operator-fee \
--chain-network base-sepolia \
--recipient 0xYourFeeRecipient \
--bps 12The arborter submits the on-chain setOperatorFee as the instance's
operator_admin; rotate that key with rotate-operator-admin.
Create markets
aspens-admin set-market \
--base-network base-sepolia \
--quote-network op-sepolia \
--base-symbol USDC \
--quote-symbol USDT \
--base-address 0xUsdc... \
--quote-address 0xUsdt... \
--base-decimals 6 \
--quote-decimals 6 \
--pair-decimals 6pair-decimals is the precision the matching engine works in
internally — it can differ from either token's native decimals. See
decimals.md
in the SDK for conversion examples.
Verify
aspens-admin status # connection check
aspens-admin version # server build info
aspens-admin balances # owner / signer / contract balances per chain4. Reference: aspens-admin commands
| Command | Notes |
|---|---|
init-admin --address <addr> | One-time, fresh stack only |
login [--chain-id N] | Sign EIP-712 with ADMIN_PRIVKEY |
update-admin <addr> | Rotate admin |
set-chain ... / delete-chain <network> | Chain registration |
set-token ... / delete-token ... | Token registration |
set-market ... / delete-market <id> | Market creation |
deploy-contract <network> --fees <bps> | Deploy MidribV3 instance (baked maintenance fee) |
set-operator-fee --chain-network <n> --recipient <addr> --bps <n> | Set the operator-fee tier |
rotate-operator-admin ... | Rotate the instance's operator_admin key |
set-trade-contract --address ... | Register an externally-deployed instance |
delete-trade-contract <network> | Unregister |
version / status / admin-public-key | Read-only info |
balances | Owner / signer / contract balances per chain |
5. Maintenance
- Image bumps: edit the
@sha256:digest ininfra/stacks/.../docker-compose.yml, then redeploy. - Database backups: back up the Postgres volume (
tee_db) and the TigerBeetle data volume (tee_tigerbeetle-data) as a pair — they are one logical datastore (Postgres holds the registry + history that names the TigerBeetle balances), so restoring one without the other strands state. - Logs:
docker stack ps <stack>for placement,docker service logs <service>for tails. - Hard reset: scale services to 0, drop
tee_dbandtee_tigerbeetle-datatogether (the registry and the ledger are a pair), then scale back up. Droptee_arborter-keysas well only when rotating the signer (that forces a contract redeploy).
The CVM stack README captures the full operational catalog (signer
rotation, GHCR token refresh, certbot retry, traefik tls.yml
re-render, etc.).
6. Going live
Before opening the stack to traders:
- Fund the admin wallet on every chain (the arborter signs net
settleBatchand withdrawal-voucher transactions from this address; insufficient native balance will hang settlement). On Tron, fund the signer's Tron account with TRX instead. - Verify every chain, token, and market with
aspens-admin status. - Run a paper trade end-to-end with
aspens-cli:
aspens-cli deposit base-sepolia USDC 100
aspens-cli buy-limit USDC/USDT 10 0.99
aspens-cli stream-trades USDC/USDT --historicalResources
- infra repo — stack definitions, justfile, scenarios
- SDK repo —
aspens-admin,aspens-cli - Telegram: @aspens_xyz
- Feedback: aspensprotocol/feedback