Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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-signer

For 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> --json

transfer, 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
FlagNotes
--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.
--usSet when paying by card from the US (Crossmint compliance).
  • coinbase{url} (Coinbase Pay) · card{checkoutUrl} (Crossmint) · qr{chainId} (prints address + QR, no URL).
  • In --json mode 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.