Skip to content

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 / cancel transactions from this account)

2. Bring up the stack

The infra/ repo contains three stack flavors:

StackPurpose
localAnvil / solana-test-validator on your laptop
googlecloudConfidential VM deploy behind your own domain
testing-suiteIntegration-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-end

Available 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-admin

Configure 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 0xYourAdminAddress

This is a one-time call. On any subsequent stack, log in to obtain a JWT:

aspens-admin login                   # uses ADMIN_PRIVKEY for EIP-712 signature

Save 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.org

The 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 6

Deploy 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 6

pair-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 chain

4. Reference: aspens-admin commands

CommandNotes
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-keyRead-only info
balancesOwner / signer / contract balances per chain

5. Maintenance

  • Image bumps: edit the @sha256: digest in infra/stacks/.../docker-compose.yml, then redeploy.
  • Database backups: TimescaleDB volume — back up via docker exec + pg_dumpall on 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_db and tee_arborter-keys volumes, 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:

  1. Fund the admin wallet on every chain (the arborter signs fill and cancel from this address; insufficient native balance will hang settlement).
  2. Verify every chain, token, and market with aspens-admin status.
  3. 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 --historical

Resources