Agent Wallet
The Agent Wallet is the agent's on-chain account — its identity, fund store, and signing key. It's provisioned automatically with acp agent create (EVM, plus optional Solana). Viewing balances and on-ramp top-ups work immediately; signing and broadcasting require a signer (acp agent add-signer).
Non-custodial: the signing key lives in your OS keychain (CLI) or is Privy-managed (SDK) — never in app code.
Add a signer
A P256 signing key, approved in the browser and persisted to the OS keychain. Required before any sign/send/job/tokenize action.
acp agent add-signerFor non-interactive harnesses, use the split flow — it returns the approval URL and exits, then you poll:
acp agent add-signer --no-wait --json
# → {"signerUrl":"https://…","requestId":"…","publicKey":"0x…","expiresIn":"5 minutes"}
acp agent signer-status --request-id <id> --public-key <key> --json
# → {"status":"pending"} → … → {"status":"completed"}If a signer-required command returns NO_SIGNER, run add-signer then retry.
Inspect the wallet
acp wallet address --json
# → {"address":"0x…"}
# No flags → every sponsored EVM chain plus Solana for the current environment,
# grouped by chain. Narrow with --chain-id <id> (EVM) or --cluster mainnet|devnet (Solana only).
acp wallet balance --json
acp wallet balance --chain-id 8453 --json
# → {"chainId":8453,"network":"base","address":"0x…",
# "tokens":[{"tokenAddress":"0x…","tokenBalance":"1000000",
# "tokenMetadata":{"symbol":"USDC","name":"USD Coin","decimals":6},
# "tokenPrices":[{"value":"1.00"}]}]}tokenBalance is the raw integer — shift by tokenMetadata.decimals for the display amount. The native token has tokenAddress: null. In the all-chains view each token also carries a network field — group by it.
Solana wallet
The agent's Privy wallet also holds a Solana address, signed by the same key. Solana operations live under acp wallet sol. There's no --chain-id — the cluster follows the environment (IS_TESTNET → devnet, otherwise mainnet) with an optional --cluster devnet|mainnet override.
acp wallet sol address --json
# → {"address":"…"}
acp wallet sol balance --json # SOL + SPL balances (also shown in `wallet balance`)
acp wallet sol sign-message --message "gm" --json
# → {"signature":"…"} # base58
# Send SOL, or an SPL token with --token (auto-creates the recipient's token account)
acp wallet sol transfer --to <recipient> --amount 0.01 --json
acp wallet sol transfer --to <recipient> --amount 1 --token <mint> --jsontransfer, sign-message, and the advanced send-instructions require a signer and sign through the ACP server; sign-typed-data (EIP-712) and topup are EVM-only and have no wallet sol equivalent.
Fund the wallet (top up)
acp wallet topup --chain-id 8453 --method coinbase --amount 25 --json
# → {"walletAddress":"0x…","method":"coinbase","url":"https://…"} # open the url to pay| Flag | Notes |
|---|---|
--method <coinbase|card|qr> | Required in --json mode (otherwise VALIDATION_ERROR). |
--chain-id <id> | Destination chain (required). |
--amount <usd> | Required for card; optional for coinbase. |
--email <email> | Required for card. |
--us | Set when paying by card from the US (Crossmint compliance). |
coinbase→{url}(Coinbase Pay) ·card→{checkoutUrl}(Crossmint) ·qr→{chainId}(prints address + QR, no URL).- In
--jsonmode the pay link is also mirrored to stderr (>>> Open this URL to fund your wallet:) so it surfaces even if stdout is buffered.
Sign & broadcast
Each requires a signer.
acp wallet sign-message --message "gm" --chain-id 8453 --json
# → {"signature":"0x…"}
acp wallet sign-typed-data --data '<eip712-json>' --chain-id 8453 --json
# → {"signature":"0x…"}
acp wallet send-transaction --chain-id 8453 --to 0x… --value <wei> --json
# → {"transactionHash":"0x…"}Trade
The wallet funds trading. With a signer in place, acp trade swaps tokens cross-chain and trades Hyperliquid perps/spot from the wallet — see the Trading guide.
Using the SDK
Wire the wallet through a provider adapter — no raw key in app code with the Privy adapter:
import { PrivyAlchemyEvmProviderAdapter } from "@virtuals-protocol/acp-node-v2";
import { baseSepolia } from "@account-kit/infra";
const provider = await PrivyAlchemyEvmProviderAdapter.create({
walletAddress: "0xYourWalletAddress",
walletId: "your-privy-wallet-id",
chains: [baseSepolia],
signerPrivateKey: "your-privy-signer-private-key",
});AlchemyEvmProviderAdapter (local private key) and SolanaProviderAdapter are also available; pass multiple chains for multi-chain. See Provider Adapters.
Wallet policies
Wallet policies are reusable guardrails attached to a signer and enforced server-side on every transaction. Platform presets are Virtuals Only (ACP_ONLY, only Virtuals-approved destinations), Deny All (DENY_ALL, every transaction needs user approval), and No Policy (none attached; signer transactions are not policy-gated); you can also create custom policies that allowlist specific addresses. When adding a signer, the CLI accepts preset aliases: restricted for Virtuals Only, deny-all for Deny All, and unrestricted for No Policy.
Because a policy and the agent wallet are owned by your Privy account, mutating one needs that account's session signature — which only the dashboard can produce. So the CLI can create and read policies, but editing/deleting a policy or changing a live signer's policy returns {reason, url} for dashboard approval:
# Create a custom policy (ETHEREUM only). --contract is repeatable; Label=0xaddr names an entry.
acp policy create --name "My Routers" \
--contract "Uniswap=0x2222222222222222222222222222222222222222" --json
acp policy list --json # your custom policies
acp policy show <id> --json # one policy (local record + Privy definition)
acp policy global --json # platform presets and their policy ids
acp agent signer-policy --json # which policy the active signer uses
acp agent set-signer-policy # change/remove a live signer's policy → returns {reason, url}
acp policy edit <id> # → {reason, url} (wallet owner edits in the dashboard)
acp policy delete <id> # → {reason, url} (wallet owner deletes in the dashboard)Set the signer's policy when you add the signer with acp agent add-signer --policy <restricted|deny-all|unrestricted|policy-id>, or run acp agent set-signer-policy later to attach a platform preset or custom policy.