Admin and operator reference for the Universal Gateway program.
All CLI commands assume you are in contracts/svm-gateway/.
One-time setup. Run once per deployment.
# Deploy program
anchor deploy --provider.cluster devnetThere is currently no standalone config:init CLI command. Bootstrap is done by calling the on-chain initialize(...) instruction from an Anchor client.
Working references:
app/gateway-test.ts— devnet bootstrap exampletests/helpers/test-setup.ts— test bootstrap flow
After initialize(...) succeeds, configure the remaining state with the CLI commands below.
Accounts created by the bootstrap flow:
ConfigPDAVaultPDAFeeVaultPDA (created lazily whenset_protocol_feeis first called)RateLimitConfigPDA (created lazily when any rate-limit config command is first called:set_block_usd_caporupdate_epoch_duration)
npm run config:tss-init -- --eth 0x<40-hex-address> --chain-id <chain-id-string>npm run config:tss-update -- --eth 0x<40-hex-address> --chain-id <chain-id-string>Only the current admin can update TSS. The TSS address is stored in TssPda and is used for ECDSA signature verification on all outbound transactions.
npm run config:authority-set -- --new-admin <new-admin-pubkey>
npm run config:authority-set -- --new-pauser <new-pauser-pubkey>
npm run config:authority-set -- --new-admin <new-admin-pubkey> --new-pauser <new-pauser-pubkey>If admin is changed, Config.admin is updated immediately. update_tss authorization follows Config.admin.
After rotating admin, update your operator signer/keypair used by CLI before running additional admin commands.
Caps are in Pyth 8-decimal USD format: 1_00000000 = $1.00
npm run config:caps-set -- --min 100000000 --max 1000000000
# Example: $1 min, $10 max
# min = 100000000, max = 1000000000Caps apply only to the instant GAS / GAS_AND_PAYLOAD routes. FUNDS routes are governed by token rate limits instead.
Flat fee charged per send_universal_tx call, paid in SOL by the depositor.
# Set fee (in lamports); this also creates FeeVault if needed
npm run config:fee-init -- --fee <lamports>
# Example: disable fee
npm run config:fee-init -- --fee 0The protocol fee is deducted from native_amount before routing. It goes to FeeVault, not Vault, preserving the 1:1 bridge invariant.
There is currently no dedicated collect_protocol_fees instruction or CLI command in this program.
The Pyth price feed is used to convert SOL amounts to USD for GAS route cap enforcement.
npm run config:pyth-set-feed -- --feed <pyth-price-feed-pubkey>
# Optional: set confidence threshold
npm run config:pyth-set-conf -- --threshold <u64>The program does not enforce a fixed feed — the admin can update it at any time via set_pyth_price_feed.
Inbound gas-route pricing enforces staleness (get_price_no_older_than) and optionally enforces confidence (pyth_confidence_threshold > 0).
Per-slot USD budget for GAS route deposits. 0 disables.
npm run config:rate-set-block-usd-cap -- --cap <u128-8-decimal-usd>Controls the period for token-based epoch rate limits.
npm run config:rate-set-epoch -- --seconds 86400Set to 0 to disable epoch-based rate limiting entirely.
Each SPL token that can be bridged must be whitelisted with an epoch threshold.
# Whitelist a token
npm run token:whitelist -- --mint <mint-pubkey-or-symbol> --threshold <token-natural-units>
# List whitelisted tokens
npm run token:listThe threshold is the maximum amount of that token that can be deposited in one epoch. Native SOL also has a rate limit entry (use Pubkey::default() as the mint when deriving the PDA).
Use for emergencies. All inbound and outbound operations revert when paused.
npm run config:pause
npm run config:unpauseEither the configured pauser or the current admin can call these. Admin and pauser can be the same or different keypairs.
npm run token:create -- --name "Test Token" --symbol TST --decimals 6
npm run token:mint -- --mint <mint-pubkey-or-symbol> --recipient <wallet-pubkey> --amount <amount>
npm run token:list # list all whitelisted tokens and their limitsAnchor programs are upgradeable by default (upgrade authority is set to the deployer keypair). To upgrade the program in place:
anchor upgrade target/deploy/universal_gateway.so \
--program-id <PROGRAM_ID> \
--provider.wallet ./upgrade-keypair.jsonThere is no admin-only vault migration instruction in the current program. If a new program ID is required (e.g., breaking account layout change), fund migration must be handled out-of-band — the current program has no on-chain path for an admin to move vault funds to a new deployment.
npm run config:showShows current values for:
- Admin, pauser addresses
- USD caps
- Pyth feed
- Protocol fee
- Pause state
- Block USD cap, epoch duration
Deposit rejected with Paused: Gateway is paused. Call unpause from the pauser address.
Deposit rejected with BelowMinCap / AboveMaxCap: native_amount (after protocol fee) is outside USD cap range. Adjust caps or deposit amount.
Outbound rejected with TssAuthFailed: TSS address mismatch or wrong message format. Verify TssPda.tss_eth_address matches the current TSS signer and message construction follows 2-WITHDRAW-EXECUTE.md.
Outbound replay attempt fails: sub_tx_id has already been finalized. The ExecutedSubTx PDA for this ID already exists, so the transaction is rejected during account initialization.
SPL deposit fails with InvalidAccount: user_token_account or gateway_token_account was passed as null on an SPL route. Both must be provided for SPL deposits.