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
fill/canceltransactions from this account)
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).
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 \
--permit2-address 0x000000000022D473030F116dDEE9F6B43aC78BA3 \
--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. 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>.
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. 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).
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 |
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: TimescaleDB volume — back up via
docker exec+pg_dumpallon the timescale service. - Logs:
docker stack ps <stack>for placement,docker service logs <service>for tails. - Hard reset: scale services to 0, drop the
tee_dbandtee_arborter-keysvolumes, scale back up.
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
fillandcancelfrom this address; insufficient native balance will hang settlement). - 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