This guide documents the production mainnet rollout for RouteDock. It assumes you already run the project on testnet and want to move to Stellar mainnet by switching STELLAR_NETWORK=mainnet with production-grade controls.
Do not deploy until every item below is explicitly marked done by the operator on call.
- Contract audit status confirmed
- Agent vault contract code reviewed internally and tagged release commit.
stellar-experimental/one-way-channelrisk accepted by engineering + security leadership (see Section 5 disclaimer).- Command:
git rev-parse HEAD
- Key custody & Cloudflare Workers secret management approved
- Signing keys live in HSM or Ledger-backed flow.
- No raw seeds stored in
.env,.dev.vars, shell history, CI logs, or chat. wrangler.jsoncis committed to git: Never place secret seeds (S...) or API keys in thevarsblock ofwrangler.jsonc.- All production secrets for Cloudflare Workers must be injected exclusively via
wrangler secret put. Local development secrets must only exist in uncommitted.dev.varsfiles. - Command (sanity check for accidental secrets in env/config files):
rg -n "(SECRET=|SEED|S[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]{55})" apps agent docs --glob "*.env*" --glob "*.dev.vars*" --glob "*.jsonc"
- Monitoring and alerting live
- Stellar Expert webhook configured for vault + channel contracts.
- Supabase alerting configured for
policy_rejectspikes. - Command:
curl -s https://stellar.expert/explorer/public | head -n 1
- Rollback plan tested
- Procedure validated to stop new sessions, rotate keys, and force session expiry.
- Last tabletop timestamp recorded.
- Command:
date -u
Use hardware-backed key management only.
- Create the key in your HSM/KMS and export only the public key.
- Store key metadata in your secrets manager (not raw seed).
- Verify account address:
# Example: read from secure runtime injection
echo "$STELLAR_PAYEE_ADDRESS"- Initialize Ledger with secure PIN and recovery phrase backup policy.
- Derive Stellar account and export public key only.
- Verify:
stellar keys address ledger-mainnet --hd-path "44'/148'/0'" --network mainnet
⚠️ CRITICAL SECURITY NOTE: Never placeS...secret seeds in.envfiles orwrangler.jsoncvars(sincewrangler.jsoncis committed to version control). Deployed Cloudflare Workers must receive secrets viawrangler secret put. For local development only, use uncommitted.dev.vars. For testing, use isolated ephemeral accounts only.
RouteDock mainnet flow expects USDC trustline to Circle's official Stellar issuer. If the payer account lacks a trustline, client.pay() now throws RouteDockTrustlineError preflight — before any transaction is submitted — with the exact remediation command.
- Issuer:
GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN
Create trustline from the funding account:
stellar tx new --source <MAINNET_SIGNER_ALIAS> --network mainnet \
change-trust \
--asset USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN \
--limit 100000Verify trustline exists:
stellar account balance <ACCOUNT_ADDRESS> --network mainnetStarting in SDK v0.1.3+, each client.pay() call runs a trustline preflight check before submitting any on-chain transaction:
- Queries Horizon for the payer account's balances.
- Checks for a balance entry matching the manifest's
assetcode (e.g.USDC). - Trustline found → caches the result for 5 minutes (keyed by
network:pubkey:asset) so subsequent payments to the same asset cost zero RPC calls. - No trustline → throws
RouteDockTrustlineErrorwith the exactstellar tx new change-trustCLI command for remediation.
The check is non-blocking in degraded scenarios: if Horizon is unreachable, the SDK logs a warning and continues without blocking the payment.
Call client.preflight(manifest) explicitly to validate a manifest's asset trustline without executing a payment.
⚠️ MANDATORY CONFIGURATION:USDC_ASSET_CONTRACTis required on mainnet for both providers and the agent. In@routedock/routedock,resolveAssetContractthrows an error ifUSDC_ASSET_CONTRACTis missing on mainnet because only testnet has a default fallback contract address.
Obtain or deploy the Stellar Asset Contract (SAC) wrapper ID for mainnet USDC and record it as USDC_ASSET_CONTRACT.
Build and deploy from contracts/agent-vault:
cd contracts/agent-vault
stellar contract build
stellar contract deploy \
--wasm target/wasm32-unknown-unknown/release/agent_vault.wasm \
--source <MAINNET_DEPLOYER_ALIAS> \
--network mainnetRecord the output as AGENT_VAULT_CONTRACT_ID.
Apply stricter production policy inputs:
- Daily cap: set conservative cap (example
25USDC/day). - Allowlist: only production provider payee accounts.
- Expiry: short session key lifetime (example 1-6 hours by ledger window).
Example environment snippet for agent runtime:
STELLAR_NETWORK=mainnet
AGENT_DAILY_CAP_USDC=25
AGENT_VAULT_CONTRACT_ID=<C...>
ALLOWED_PAYEES=<G...>,<G...>
SESSION_EXPIRY_LEDGERS=450
⚠️ UNAUDITED CONTRACT NOTICE:stellar-experimental/one-way-channelis unaudited. Mainnet usage requires explicit organizational risk acceptance.
Deploy from upstream repo:
git clone https://github.com/stellar-experimental/one-way-channel
cd one-way-channel
stellar contract build
stellar contract deploy \
--wasm target/wasm32v1-none/release/one_way_channel.wasm \
--source <MAINNET_DEPLOYER_ALIAS> \
--network mainnetRecord output as CHANNEL_CONTRACT_ID and set in provider + agent environments.
RouteDock mainnet x402 uses OpenZeppelin Channels facilitator at:
https://channels.openzeppelin.com/x402
Provision API token and configure bearer auth:
export OPENZEPPELIN_API_KEY="<oz-token>"
curl -i https://channels.openzeppelin.com/x402 \
-H "Authorization: Bearer $OPENZEPPELIN_API_KEY"Rotation guidance:
- Create new token.
- Store securely via
wrangler secret put OPENZEPPELIN_API_KEY. - Revoke old token after 100% cutover.
- Document rotation timestamp and owner.
Both provider-a and provider-b run as Cloudflare Workers.
⚠️ CUTOVER ORDERING CONSTRAINT (#265): Do not redeploy providers to mainnet individually before the SDK package publishes to npm. Follow the strict ordering from tracking issue #265:
- Publish
@routedock/routedock@0.2.0to npm (which includessignature_version: '2').- Configure production secrets via
wrangler secret puton both Workers.- Redeploy both providers simultaneously (
pnpm --filter provider-a deploy&pnpm --filter provider-b deploy).- Verify both serve
signature_version: '2'and are reachable before sending agent traffic.
Public variables in apps/provider-a/wrangler.jsonc (committed):
Upload sensitive secrets using wrangler secret put:
cd apps/provider-a
wrangler secret put STELLAR_PAYEE_SECRET
wrangler secret put STELLAR_PAYEE_ADDRESS
wrangler secret put OPENZEPPELIN_API_KEY
wrangler secret put SUPABASE_SERVICE_KEY
wrangler secret put USDC_ASSET_CONTRACTDeploy Provider A:
wrangler deployPublic variables in apps/provider-b/wrangler.jsonc (committed):
{
"name": "routedock-provider-b",
"vars": {
"STELLAR_NETWORK": "mainnet",
"USDC_ASSET_CONTRACT": "<MAINNET_USDC_SAC_CONTRACT_ID>",
"SUPABASE_URL": "https://<your-project>.supabase.co"
}
}Upload sensitive secrets using wrangler secret put:
cd apps/provider-b
wrangler secret put STELLAR_PAYEE_SECRET
wrangler secret put STELLAR_PAYEE_ADDRESS
wrangler secret put CHANNEL_CONTRACT_ID
wrangler secret put COMMITMENT_PUBLIC_KEY
wrangler secret put SUPABASE_SERVICE_KEY
wrangler secret put USDC_ASSET_CONTRACTDeploy Provider B:
wrangler deployThe agent runner runs as a standalone Node service configured via agent/.env:
agent/.env:
STELLAR_NETWORK=mainnet
AGENT_SECRET=<S...>
AGENT_VAULT_CONTRACT_ID=<MAINNET_AGENT_VAULT_CONTRACT_ID>
USDC_ASSET_CONTRACT=<MAINNET_USDC_SAC_CONTRACT_ID>
PROVIDER_A_URL=https://api-a.routedock.xyz
PROVIDER_B_URL=https://api-b.routedock.xyz
SUPABASE_URL=https://<your-project>.supabase.co
SUPABASE_ANON_KEY=<your-anon-key>curl -s https://api-a.routedock.xyz/.well-known/routedock.json | jq '.network,.pricing.x402.facilitator'
curl -s https://api-b.routedock.xyz/.well-known/routedock.json | jq '.network,.pricing["mpp-session"].channel_contract'
curl -s https://api-a.routedock.xyz/health
curl -s https://api-b.routedock.xyz/healthConfigure alerts for:
- agent vault contract invocations
- channel open/close transactions
- failed transactions involving payee accounts
Example webhook filter values:
network=public
entity=<AGENT_VAULT_CONTRACT_ID>
entity=<CHANNEL_CONTRACT_ID>
entity=<PAYEE_G_ADDRESS>
Track local policy enforcement failures from tx_log.
SQL check:
select created_at, tx_type, error
from public.tx_log
where tx_type = 'policy_reject'
order by created_at desc
limit 50;Alert threshold example:
- warning:
>= 5rejects in 10 minutes - critical:
>= 20rejects in 10 minutes
- Freeze autonomous traffic by disabling agent runs.
- Expire active session keys (set immediate/near-immediate ledger expiry).
- Revoke compromised API tokens (OZ + service credentials).
- Review latest on-chain tx + Supabase logs.
- Generate new hardware-backed key.
- Update key secrets in Cloudflare Workers (
wrangler secret put STELLAR_PAYEE_SECRET/STELLAR_PAYEE_ADDRESS). - Shift traffic to new key and redeploy.
- Revoke old key and archive incident record.
Set minimal session expiry in agent/.env:
SESSION_EXPIRY_LEDGERS=5Then restart agent service:
pnpm --filter agent startThis keeps custody with primary vault controls while rapidly reducing session key blast radius.
{ "name": "routedock-provider-a", "vars": { "STELLAR_NETWORK": "mainnet", "USDC_ASSET_CONTRACT": "<MAINNET_USDC_SAC_CONTRACT_ID>", "SUPABASE_URL": "https://<your-project>.supabase.co" } }