@fastxyz/cli
v1.7.1
Published
Fast CLI - Account, network, and transaction management
Keywords
Readme
fast CLI
A command-line tool for the Fast network — manage accounts, send tokens, bridge assets between EVM chains and Fast, add fastUSD through supported funding routes, and pay x402-protected APIs.
Installation
Requires Node.js 20, 22 or newer
pnpm install -g @fastxyz/cliOr use via pnpm in the repo:
pnpm cli --helpVerify:
fast --version
fast --helpQuick Start
# Create an account
fast account create
# Check balances
fast info balance
# Send the network's default token (fastUSD on mainnet) — Fast → Fast
fast send fast1abc...xyz 10
# Send a specific token explicitly
fast send fast1abc...xyz 10 --token USDC
# Attach a Fast UserData memo (32 UTF-8 bytes maximum)
fast send fast1abc...xyz 10 --token USDC --memo "invoice-42"
# Ask someone to pay you 10 fastUSD: prints an app.fast.xyz link to share (mainnet only)
fast request 10 --network mainnet
# Bridge USDC from Arbitrum Sepolia to Fast (--token USDC is required on
# mainnet because the network default — fastUSD — is not on EVM chains)
fast fund usdc crypto 50 --chain arbitrum-sepolia --token USDC
# Choose one of the four supported Fast app funding routes (mainnet only)
fast fund --network mainnet --amount 50
# Or choose a specific route directly
fast fund card --network mainnet --amount 50
fast fund usdc --network mainnet
fast fund crypto --supplier coinbase --network mainnet
# Pay an x402-protected API
fast pay https://api.example.com/resourceEnvironment
| Variable | Description |
| --------------- | ---------------------------------------------------- |
| FAST_PASSWORD | Keystore password (preferred over --password flag) |
AI Agent Skill
Install the skill to let AI agents operate the fast CLI on your behalf:
npx skills add https://github.com/fastxyz/fast-sdk/tree/main/skillsDocumentation
Full command reference and workflows: skills/fast/SKILL.md
Global Options
These options work with every command:
| Option | Description |
|--------|-------------|
| --json | Emit machine-readable JSON output instead of human-readable text |
| --network <name> | Override the active network (mainnet, testnet) for this command |
| --account <name> | Use a specific named account instead of the default |
| --password <pwd> | Provide the keystore password (defaults to FAST_PASSWORD env var, then interactive prompt) |
| --non-interactive | Auto-confirm confirmations and fail when required input is missing |
| --debug | Enable verbose debug logging to stderr |
Default token
Commands that accept --token (currently fast send and fast fund usdc
crypto) default to the active network's defaultToken.symbol when the flag
is omitted: fastUSD on mainnet, testUSDC on testnet. Bridge routes
additionally require the resolved token to exist on the target chain — on
mainnet bridges you must pass --token USDC explicitly because fastUSD does
not exist on EVM chains.
To customize the default, register a network with fast network add <name>
--config <path> where the JSON config includes a defaultToken field, for
example:
{
"defaultToken": {
"symbol": "USDC",
"tokenId": "0x...",
"decimals": 6
}
}If a custom network omits defaultToken, callers must pass --token
explicitly; otherwise the command errors with INVALID_USAGE.
Commands
fast multisig
Create or import a Rust-compatible multisig wallet, inspect its current-nonce pending transactions, and co-sign them:
fast multisig init --name treasury --signers alice,bob,fast1... --quorum 2 --config-nonce 0
fast multisig import --from ./wallet.json --name treasury
fast multisig pending --account treasury --as alice
fast multisig vote --account treasury --as alice --tx 0x...Signer order is canonicalized by decoded 32-byte address (not bech32 text).
vote prints the complete transaction before an interactive signature. Export
uses exclusive file creation and never overwrites an existing file.
Multisig AllSet withdrawals (two signing rounds)
Fast → EVM withdrawals support a multisig account with an explicit local recovery journal. Each signer keeps their own key; the coordinator never needs the other signer's seed. EVM → Fast deposits still require a single-signer account.
Create a private directory once, then initiate the withdrawal:
mkdir -p "$HOME/.fast/withdrawals"
chmod 700 "$HOME/.fast/withdrawals"
fast send 0xRECIPIENT 1000 --token USDC --to-chain polygon \
--network mainnet --account treasury --as alice \
--withdrawal "$HOME/.fast/withdrawals/rebalance.json"The coordinator's first call proposes the Fast token transfer to the bridge.
The cosigner independently checks network, bridge, token and amount, then votes for the exact hash returned by the coordinator:
fast multisig pending --network mainnet --account treasury --as bob fast multisig vote --network mainnet --account treasury --as bob --tx 0xTRANSFER_HASHAfter quorum, the coordinator repeats the exact same
sendcommand and file. This proposes the linked AllSet intent, not another token transfer.The cosigner inspects pending again and votes with
--tx 0xINTENT_HASH. The summary decodes the linked transfer, token, recipient and deadline, alongside the raw claim. Check the destination chain and bridge against the first round; decoding is not authentication. The intent expires 24 hours after its creation.The coordinator repeats the same command to cross-sign, simulate execution and submit to the relayer. Repeat it later to observe settlement. Only a successful matching EVM Withdraw receipt and token payment tied to that withdrawal's executor action produce
completed; relayer acceptance alone remainsawaiting-settlement. Settlement also requires the bridge's executor configuration at the receipt's block; an RPC failure leaves the withdrawal pending rather than confirming payment.
Safety and recovery:
- Multisig withdrawal journals support macOS and Linux only; Windows is rejected before opening the journal. Private POSIX ownership and permissions are required.
- RPC, cross-sign and relayer endpoints may rotate during recovery. The signed route remains fixed, and changing endpoints never retries an uncertain attempt.
- Keep the journal until settlement. It contains public signed envelopes, never seeds. Keep the directory private (0700) and the journal private (0600).
- Never use another file to retry a withdrawal. That means a new payment. Use one coordinator and serialize treasury activity across devices; the local lock is not a distributed exactly-once guarantee.
- Each signed transaction is saved before submission. A lost response is reconciled by exact certificate/proposal identity, never by amount or timing. An uncertain submission is not automatically resent. Missing proposals, expired intents, rejected/uncertain relay attempts and revoked transfers require manual recovery of the original transfer, not a new payment. Revoke is not implemented by this command.
- A crash can leave
FILE.lock. Reconcile the original hashes and account state before manually removing that lock. Do not edit or delete journal stages. - RPC chain, bridge binding, token mapping, pause state and available liquidity are checked before payment. These are snapshots, not reservations. Keep enough Fast gas for both signing rounds; later liquidity or service changes can still require recovery. A successful simulation is not a settlement guarantee.
--memo,--replace-pendingand--eip-7702are not supported for this flow.
Single-signer withdrawals keep their existing behavior and do not use this journal.
Initiating send or token operations performs a best-effort preflight and
refuses when it observes another multisig proposal at the current nonce.
Inspect it with fast multisig pending; only pass --replace-pending when
replacing that observed proposal is intentional. This check is not atomic with
submission: concurrent initiators can both observe an empty pending set, and
the proxy may then replace the first proposal. A voter that fetched proposal A
before an explicit replacement with B can likewise submit its stale vote and
restore A. Strict no-replacement semantics require operator-wide serialization
across initiation, replacement, and voting; after replacement, all stale voting
sessions must be abandoned. A future conditional-submit primitive in the proxy
is required to enforce this invariant server-side.
If the proxy accepts a signed envelope but its response is lost, the CLI emits
TX_SUBMISSION_UNKNOWN with the precomputed transaction hash, nonce, and exact
signed REST envelope. Do not rerun the original command: first reconcile that
identity against the explorer, account nonce, and pending multisig proposals.
Where resubmission is appropriate, resubmit the preserved envelope rather than
building a new transaction with a new timestamp or nonce.
fast account create
Create a new Ed25519 account and store it in the local keystore.
fast account create --name my-accountOptions: --name <alias> — Optional human-readable alias for the account.
Prompts for an optional keystore password if --password is not given and FAST_PASSWORD is unset.
fast account import
Import an existing account from a 32-byte hex private key.
fast account import --name my-imported-account --private-key 0x...
fast account import --name my-signer --legacy-keystore /path/to/legacy-keystore.jsonOptions:
--name <alias>— Optional human-readable alias for the imported account--private-key <hex>— Hex-encoded 32-byte private key--key-file <path>— Path to a JSON file containing aprivateKeyfield--legacy-keystore <path>— Encrypted JSON keystore from the original Rust multisig CLI
If --name is omitted, the CLI auto-generates one. Provide exactly one key source. The legacy option prompts for its password, verifies the file's Fast address, and stores the imported key encrypted. The original file is not changed.
fast account list
List all stored accounts with their addresses and default status.
fast account listfast account export
Export an account's private key (requires password).
fast account export my-accountIf no account name is provided, the CLI exports the current default account.
fast account set-default
Set the default account for commands that need a signer.
fast account set-default my-accountfast account delete
Delete an account from the local keystore.
fast account delete my-accountfast send <address> <amount>
Send tokens between Fast and supported EVM chains.
fast send fast1recipient... 10.5 --token USDC
# Send to a Fast ID name; the CLI resolves it to the bound fast1... address first
fast --network mainnet send alice.smith 10
# Withdraw USDC from Fast mainnet to Polygon mainnet
fast --network mainnet send 0x1234567890123456789012345678901234567890 0.01 --to-chain polygon --token USDCPositional arguments:
<address>— Recipient: afast1...address (Fast), a0x...address (EVM), or a Fast ID name such asalice.smith. Names are resolved on the current network (mainnet or testnet) before anything is signed; the confirmation showsalice.smith (fast1...), and--jsonoutput addstoName. Resolution fails closed: an unregistered name exits withINVALID_ADDRESS, and an unreachable or inconsistent registry exits withFAST_ID_RESOLUTION_FAILED, in both cases without sending. Names can't be used with--to-chain, which needs a0x...recipient.<amount>— Human-readable amount (for example,10or1.5)
Options:
--from-chain <chain>— Source EVM chain for EVM → Fast transfers--to-chain <chain>— Destination EVM chain for Fast → EVM transfers--token <token>— Token symbol or token ID. Defaults to the network'sdefaultToken.symbol(fastUSDon mainnet,testUSDCon testnet). Bridge routes require the resolved token to be available on the target chain; otherwise the command errors withCommandUnsupportedForTokenError.--eip-7702— Use the smart deposit flow for EVM → Fast transfers--account <name>— Sender account (defaults to the configured default)
fast request <amount>
Create a payment-request link: a URL that opens the Fast app's Send screen
with the recipient and amount filled in. Share it with whoever should pay you;
they review and confirm the transfer in the app. Nothing is signed or sent by
this command, and no password is needed. It works offline unless --to is a
Fast ID name (resolved on the Fast ID registry first) or --wait is set
(watches the explorer for the payment).
fast request 10 --network mainnet
# → https://app.fast.xyz/send?to=fast1...&amount=10
# Machine-readable, with a QR code image to share
fast request 10 --network mainnet --qr-file request.svg --json
# Ask for payment to another address you control, or to a Fast ID name
fast request 2.50 --network mainnet --to fast1someoneelse...
fast request 2.50 --network mainnet --to alice.smith
# Print the link, then block until the payment arrives (default timeout 300 s)
fast request 10 --network mainnet --wait
# With --json, stdout stays empty until the payment arrives or the wait times out, so
# share the QR code this same command writes before it starts waiting
fast request 10 --network mainnet --wait --qr-file request.svg --json
# If the link was shared from an earlier `fast request ... --json`, wait on that request:
# a new `request --wait` only counts payments made after it starts
fast wait-for-payment --amount 10 --to <data.address> --since <data.createdAt> --network mainnet --jsonPositional arguments:
<amount>— Amount of fastUSD to request: a positive decimal with at most 6 decimal places (10,2.50,.5). It is normalized (010.50→10.5) before going into the link.
Options:
--to <fast1...|name>— Who is to be paid (default: the active account; with a multisig wallet, the wallet address): a validfast1...address or a Fast ID name such asalice.smith. A name is resolved on mainnet first and fails closed like infast send(INVALID_ADDRESSif unregistered,FAST_ID_RESOLUTION_FAILEDif the registry can't be read). The link then carries the name (?to=alice.smith), so the payer sees it and the app resolves it again at payment time;--jsonreturns the resolvedaddressplustoName.--qr— Also print the link as a QR code in the terminal. With--json, the QR code goes to stderr so stdout stays a single JSON document.--qr-file <path.svg>— Write the link as an SVG QR code to this path (overwritten if it exists).--wait— Wait until the payee receives exactly this amount of fastUSD from another address, sent after the request was created (same matching asfast wait-for-payment). Human output prints the link before waiting. With--json, stdout stays empty until the command finishes (the result then carriesurlandpayment); only the--qr(stderr) and--qr-fileQR codes come out before the wait. Fails withPAYMENT_TIMEOUT(exit 1) if no matching payment is confirmed in time. If the final explorer poll did not complete, the error reports incomplete verification rather than claiming no payment was observed. The error message repeats the link and when the request was created (created <createdAt>), the--sincefor waiting again withfast wait-for-payment.--timeout <seconds>— With--wait, how long to wait (default: 300, at most 2147483, about 24 days). Rejected without--wait.
Output (--json):
{
"ok": true,
"data": {
"url": "https://app.fast.xyz/send?to=fast1...&amount=10",
"address": "fast1...",
"amount": "10",
"token": "fastUSD",
"network": "mainnet",
"createdAt": "2026-10-02T12:00:00.000Z",
"qrFile": "/abs/path/request.svg"
}
}qrFile is present only with --qr-file. createdAt is when the request was made;
pass it as --since to fast wait-for-payment to wait for this request later.
With --wait, data also has a payment object with the matching transfer
(hash, type, from, to, amount in base units, formatted, tokenName,
tokenId, timestamp, explorerUrl).
Requirements:
- Mainnet only (the Fast app runs on mainnet); other networks exit with
INVALID_USAGE. - The link does not prove anything was paid. Confirm the payment with
--wait,fast wait-for-payment --since <createdAt>, orfast info history --direction in.
fast info balance
Check balances for the current account or a named account.
# By account name
fast info balance --account my-account
# For a specific token
fast info balance --token USDCfast info status
Check the current network configuration and whether the Fast RPC is reachable.
fast info statusfast info tx <hash>
Look up a transaction by hash in the local CLI history store.
fast info tx 0xabc123...fast info history
Show the selected account's transaction history (--account, else the default account): transfers read from the network's explorer API, including payments received from other accounts and EVM → Fast deposits, merged with the transactions this CLI recorded locally. Newest first.
fast info history --limit 20
# Only incoming payments, e.g. "did Leo pay me?"
fast info history --direction in --from fast1leo... --json
# Only what this CLI recorded locally, for every local account (no explorer lookup)
fast info history --localOptions:
--direction <in|out|all>— Only incoming (in), only outgoing (out), or everything (all, default). Self transfers only appear withall.--local— Previous behaviour: list only the local log (what this CLI submitted), for every local account, without calling the explorer API. Add--account <name>to narrow it to one account. Received payments do not appear. As before, pending bridge entries are still re-checked against the AllSet portal, so the command is not fully offline.--from <address>— Filter by sender address--to <address>— Filter by recipient address--token <token>— Filter by token name or token ID (a symbol also matches its token ID on the current network)--limit <n>— Max number of records to return--offset <n>— Number of records to skip
Each row has the same fields as before plus direction (in, out or self, relative to the account) and source (network or local). A network transfer that this CLI also recorded locally (same Fast transaction hash) is shown once, as the local entry. --json output also includes account and a warnings array. If the explorer API is unreachable, or the network has no explorerApiUrl, the command still succeeds with local history only and reports why in warnings (and on stderr). A warning also appears if paging stopped before enough network rows matched the filters. Whenever warnings is non-empty, missing incoming payments do not mean nothing arrived. With no account at all, it lists the whole local log as before.
For new EVM → Fast deposits, the CLI records the direction relative to the submitting account, so deleting that account does not change the local classification. Older entries without a recorded direction use the sender account if it still exists; otherwise they default to out. That legacy fallback is not proof that the deposit was not self-funded.
fast wait-for-payment
Block until a matching incoming payment arrives, then print it. It polls the network's explorer API every 2 seconds and matches an incoming Fast transfer or EVM → Fast deposit (Mint) to the watched address with exactly the given amount and token, sent at or after --since.
# Wait up to 5 minutes for exactly 25 fastUSD from a specific sender
fast --network mainnet wait-for-payment --amount 25 --from fast1leo... --json
# Watch another address for 0.5 testUSDC sent since 12:00 UTC, for 10 minutes
fast --network testnet wait-for-payment --amount 0.5 --to fast1... --since 2026-10-02T12:00:00Z --timeout 600Options:
--amount <amount>— Exact amount expected, human-readable (required). Converted with the token's decimals; the payment must match to the base unit.--token <token>— Token symbol or token ID. Defaults to the network'sdefaultToken.symbol.--from <fast1...>— Only accept a payment from this sender--to <fast1...>— Address to watch (default: the active account)--since <iso-time>— Only accept payments submitted at or after this time (default: when the command starts). Times without a zone are read as UTC.--timeout <seconds>— Give up after this many seconds (default: 300, at most 2147483, about 24 days)
--json returns { ok: true, data: { hash, type, from, to, amount, formatted, tokenName, tokenId, timestamp, explorerUrl, network } }. If no matching payment is confirmed in time, it exits 1 with PAYMENT_TIMEOUT; explorer errors while polling are retried until then. If the last poll did not complete, the timeout message describes incomplete verification, since an earlier page may already have shown a match that could not yet be confirmed as the earliest. A timeout does not prove that no payment arrived. Networks without an explorer API fail with EXPLORER_NOT_CONFIGURED.
fast info bridge-chains
List EVM chains available for Fast-EVM transfers.
fast info bridge-chainsfast info bridge-tokens
List tokens available for Fast-EVM transfers and the chains they are configured on.
fast info bridge-tokensfast fund app funding routes
fast fund interactively selects one of the four supported funding routes. All
four routes credit the Fast account as fastUSD; USDC is an external source
asset, not a separate native USDC balance on Fast.
# Interactive selector
fast fund --network mainnet
# Direct routes
fast fund card --network mainnet [--address fast1...] [--amount 25]
fast fund usdc --network mainnet [--address fast1...]
fast fund crypto --supplier coinbase --network mainnet [--address fast1...] [--amount 25]
fast fund crypto --supplier swapper --network mainnet [--address fast1...] [--amount 25]The URLs use only the currently supported app routes:
| Method | App URL |
|---|---|
| Card | https://app.fast.xyz/card?to=<fast-address> |
| USDC from another network | https://app.fast.xyz/usdc?to=<fast-address> |
| Coinbase | https://app.fast.xyz/crypto?supplier=coinbase&to=<fast-address> |
| Swapper | https://app.fast.xyz/crypto?supplier=swapper&to=<fast-address> |
Hosted links are mainnet-only. --address defaults to the active account;
--amount optionally prefills the route when supported. In --json or
--non-interactive mode, choose a method explicitly instead of invoking the
interactive selector.
fast fund usdc fiat remains as a deprecated alias for fast fund card.
fast fund fastusd remains as a deprecated alias for the interactive selector.
fast fund usdc crypto <amount> — EVM bridge
Bridge USDC from an EVM chain to the Fast network. USDC is the external source asset; the Fast-side asset is selected by the configured route (for example, fastUSD on mainnet and testUSDC on testnet).
fast fund usdc crypto 10.5 --chain arbitrum-sepolia --token USDCPositional arguments:
<amount>— Human-readable amount to bridge (e.g.,10.5).
Options:
--chain <chain>— Source EVM chain (required).--token <token>— Token symbol or token ID. Defaults to the network'sdefaultToken.symbol. On mainnet that'sfastUSD, which is not on EVM chains — pass--token USDCexplicitly for the bridge case, or the command errors withCommandUnsupportedForTokenError.--eip-7702— Use the smart deposit flow (gas paid in USDC via paymaster).
Legacy fast fund fastusd
This deprecated alias now opens the interactive funding-method selector. Prefer
fast fund for the selector or an explicit route such as fast fund card.
In JSON or non-interactive mode, specify a route directly.
fast authorize request
Generate a one-time HPKE authorization request. The command creates a URL for the wallet user to open, and stores a short-lived key in ~/.fast/handover-pending.json (mode 0600, up to 5 minutes).
fast authorize request [--requester NAME] [--url URL]Options:
--requester <name>— Optional label shown to the wallet user to identify who is requesting--url <url>— Override the wallet base URL the auth link points to (default:https://app.fast.xyz/authorize). Useful for local dev against a non-production wallet, e.g.--url http://localhost:3000/authorize.
Output (human):
Authorization URL:
https://app.fast.xyz/authorize?data=eyJ...
Fingerprint: 123456
Expires at: 2026-05-25T12:05:00Z
Show the URL to the wallet user. Once they paste back the handover
code, run:
fast authorize complete --message '<paste here>'Output (--json):
{
"auth_url": "...",
"request_fingerprint": "123456",
"request_expires_at": "2026-05-25T12:05:00Z"
}Security note: ~/.fast/handover-pending.json contains the HPKE one-time private key — not the account seed. An attacker who steals only this file cannot derive the account key; they would also need to intercept the handover code the wallet user pastes back. The file expires within 5 minutes and is deleted after a successful authorize complete.
fast authorize complete
Decrypt a handover code returned by the wallet user and print the account private key to stdout.
fast authorize complete [--message TEXT | --stdin] [--print-account] [--json]Options:
--message <text>— Handover code or chat message containing it (bare base64url or quoted)--stdin— Read the handover code from stdin (e.g.,cat code.txt | fast authorize complete --stdin)--print-account— Also print the derived Fast address and public key--json— Emit JSON output
If neither --message nor --stdin is provided, the CLI prompts interactively. With --non-interactive, absence of both flags is an error.
Output (human, default):
0x1313131313131313131313131313131313131313131313131313131313131313Output (human, --print-account):
Private key: 0x1313...
Address: fast1abc...xyz
Public key: 0xdead...beefOutput (--json):
{ "private_key": "0x1313..." }After a successful decryption, ~/.fast/handover-pending.json is deleted. On decryption failures, the state file is updated (or deleted after 3 attempts) so the same request may be retried.
fast pay <url>
Pay for an x402-protected HTTP resource. The CLI handles the 402 response, signs and submits payment, and retries the request automatically.
fast pay https://api.example.com/premiumPositional arguments:
<url>— URL of the x402-protected resource (required)
Options:
--dry-run— Show payment details without actually paying--method <GET|POST|...>— HTTP method (default: GET)--header <key:value>— Custom request header (can be repeated)--body <data>— Request body (use@filepathto read from a file)--network <name>— Network to use for payment
Example with headers and body:
fast pay https://api.example.com/analyze \
--method POST \
--header "Content-Type: application/json" \
--body '{"text": "hello world"}'fast network list
List all configured networks and show which one is the current default.
fast network listfast network add <name>
Add a custom network configuration by name.
fast network add my-custom-net --config ./network-config.jsonPositional arguments:
<name>— Unique name for the network
Options:
--config <path>— Path to a JSON file with the network configuration (required)
Example JSON config (network-config.json):
{
"url": "https://api.fast.xyz/proxy-rest",
"networkId": "fast:testnet",
"chainType": "fast"
}Add "explorerApiUrl" (the explorer indexer API base, e.g. https://testnet.api.fast.xyz) to enable network history and fast wait-for-payment on a custom network; without it, fast info history shows local history only.
fast network set-default
Set the default network for all subsequent commands.
fast network set-default testnetfast network remove
Remove a previously added network configuration.
fast network remove my-custom-netConfiguration Files
The CLI stores data in ~/.fast/:
~/.fast/
fast.db # SQLite database (accounts, networks, history, encrypted keys)Environment Variables
| Variable | Description |
|----------|-------------|
| FAST_PASSWORD | Default keystore password (avoids interactive prompt) |
Examples
Full workflow: fund, check balance, and send
# 1. Create an account
fast account create --name my-account
# 2. Choose a hosted funding method; all routes credit fastUSD
fast fund --network mainnet --address fast1...
# 2b. Or select the Card route directly
fast fund card --network mainnet --address fast1...
# 3. Check your balance
fast info balance --account my-account
# 4. Send tokens to someone
fast send fast1recipient... 1.25 --account my-account --token USDCPay for an x402-protected API
# Simple GET request
fast pay https://api.example.com/premium
# POST with JSON body
fast pay https://api.example.com/analyze \
--method POST \
--header "Authorization: Bearer $API_KEY" \
--body '{"query": "summarize this text"}'
# Dry run to see payment details first
fast pay https://api.example.com/premium --dry-runSee Also
- Root README for monorepo overview
- @fastxyz/sdk for SDK documentation — the CLI uses this internally for signing and REST API calls
- @fastxyz/allset-sdk for bridging details
- Fast Documentation for protocol-level details
