Skip to content

Agent wallets & spend limits

Pro- and Max-tier accounts can attach additional Solana or EVM wallets to their Candle identity, beyond the primary embedded wallet used for headless launches. A linked wallet can pay for its own self-signed launch or trade, or simply count toward attribution.

PathGrantsUse it for
ImportA Candle-managed signer and a spend policy, so the agent key can sign with itSelf-signed launches, linked-payer trades
Link existingAttribution only, no signer, no spend authorityCounting an already-owned wallet’s activity toward the account

Both kinds count toward the tier’s linked-wallet cap (10 on Pro, 1,000 on Max) and are revocable at any time.

Importing is a ciphertext-only exchange: Candle’s server never receives, stores, or logs the wallet’s plaintext private key, at any point.

The interactive path is the CLI: candle wallets import reads the key from a file or a hidden prompt and stores the generated signer in your OS keychain, so nothing sensitive ever sits in a shell history or a loose file. The SDK’s KeychainSecretStore reads that same storage, so a CLI-imported wallet is immediately usable from an agent process: pass secretStore: KeychainSecretStore.detect() ?? undefined in the client options and linked-wallet signing finds the key itself. Programmatically, the SDK’s CandleClient.importWallet({ chain, address, privateKey, signerPublicKey, label? }) does the same work locally:

  1. POST /api/v1/agent/wallets/import/init returns Privy’s HPKE receiver public key for the wallet’s chain and address.
  2. Client-side, never sent to Candle. The private key is decoded to raw bytes and HPKE-sealed to that public key. Only the resulting ciphertext, an encapsulated key, and a caller-generated signer public key ever leave the calling process.
  3. POST /api/v1/agent/wallets/import/submit posts just those three values. Candle registers a signer from the public key and attaches a default-deny spend policy to it atomically, in the same call, so an agent-controlled signer is never created without a policy already on it, then links the wallet.
{ "success": true, "id": "...", "address": "...", "chain": "solana", "privyWalletId": "..." }
POST /api/v1/agent/wallets

Attaches a wallet Candle’s own Privy project already manages on the caller’s account, for attribution only (agent key or Privy session). The address is verified against the caller’s Privy account first, so an account can never claim a wallet it does not own. No signer or policy is created; there is nothing spend-capable to revoke later.

GET /api/v1/agent/wallets

Lists the account’s linked wallets, active ones first.

DELETE /api/v1/agent/wallets/:id

Tombstones the wallet immediately; an unknown or foreign id 404s either way. For an imported (spend-capable) wallet, revocation also neutralizes its Privy spend policy back to default-deny, reported as policyNeutralized: true/false. The field is omitted for an attribution-only link, which never had a policy to neutralize.

Every linked wallet, spend-capable or attribution-only, counts toward trade attribution once linked: a transaction it signs is recorded against the Candle account that linked it, exactly like a transaction from the account’s own primary wallet. This applies both to activity an agent reports itself and to trades the Hood curve-trade ingester picks up directly from the chain.

Agent profiles: which wallets a key can use

Section titled “Agent profiles: which wallets a key can use”

An API key is an agent profile: it has a name, an avatar, a stable profileId that survives key rotation, and its own set of wallets.

By default a profile spends only from the wallets assigned to it. Assignment is many-to-many in both directions — one profile can hold several wallets, and several profiles can hold the same wallet (a read-only reporting profile and a trading profile over one treasury wallet is the usual reason).

ScopeMeaning
selectedThe profile can spend only from its assigned wallets. This is the default for every profile created since profiles shipped.
allThe profile can spend from every wallet on the account. Keys issued before profiles existed are all, so nothing they could do before stopped working.

Scoping applies to linked wallets. It does not restrict the account’s own embedded launch wallet, which is reached through delegation rather than assignment.

The profile that imports a wallet is assigned it automatically, so the usual keys createwallets import → trade flow needs no extra step.

Terminal window
candle keys wallets ck_live_abcd1234
candle keys wallets set ck_live_abcd1234 --wallets wal_1,wal_2
candle keys wallets scope ck_live_abcd1234 --scope selected

The same three operations exist over HTTP (GET/PUT /agent/keys/{prefix}/wallets, PUT /agent/keys/{prefix}/wallet-scope), in the SDK (getProfileWallets, setProfileWallets, setProfileWalletScope) and as MCP tools (candle_get_profile_wallets, candle_set_profile_wallets).

Setting the wallets replaces the whole set: a wallet left out of the list loses access, and an empty list leaves the profile with none. Read walletScope before drawing any conclusion from the list — an empty list means every wallet under all and no wallet under selected.

A trade naming a wallet the profile does not hold is refused at build time, before anything is signed, with a message naming the fix.

What is per profile and what is per account

Section titled “What is per profile and what is per account”
ScopeNotes
Wallet accessPer profileThe assignment set above.
Per-asset max-per-transaction capPer profile, falling back to the accountA profile’s own cap replaces the account cap for the assets it names; assets it does not name fall back.
Windowed USD volume cap (txLimit)Per profileDaily, weekly, monthly or lifetime. Independent of the per-asset caps; whichever binds tighter wins.
Request rate limitPer profileEach key carries its own rateLimitPerMin.
Daily launch capBothEach profile carries its own cap value, but all of them draw down a single per-account counter, so two profiles cannot each spend the full allowance.
Linked-wallet capPer accountSet by tier. Assigning a wallet to several profiles does not consume it more than once.
Tier and its floorsPer accountA profile cannot exceed what the account’s tier allows.
Trading and launch feesPer accountCharged on the trade, identical whichever profile submitted it.
Usage meteringPer profileTrade volume, trade count and launches are recorded against the acting key, so each profile’s activity is separable.
Fee totalsPer accountAccumulated per account and asset. GET /agent/keys/{prefix}/pnl also reports the fees charged against one profile’s own fills.
Realized and unrealized P&LPer profileComputed from the profile’s own fills. Unrealized marks the open positions against current prices and is reported separately from realized, alongside how many positions could not be priced and how old the marks are. Deposits and withdrawals are excluded — funding a wallet is not profit.

The rule of thumb: anything that protects the account from a key is per profile; anything the account pays for or is entitled to is per account.

Every agent-controlled signer, the account’s own delegated launch wallet and every spend-capable linked wallet, starts unlimited: no cap exists until the account sets one. Limits live in three layers, and the narrowest one that names an asset wins:

ScopeGovernsEnforced
mainThe account’s own delegated walletServer-side, when Candle builds a launch dev buy or a trade
linkedEvery spend-capable linked walletServer-side at build time, and compiled into each wallet’s own Privy policy
Per keyOne API key, per asset, via PUT /api/v1/agent/keys/:prefix/limitsServer-side at build time, replacing the account scope for any asset it names

A key’s cap replaces the account’s for the assets it names rather than combining with them; an asset the key does not name still falls through to the account scope. A SPEND_LIMIT_EXCEEDED response says which layer bit, in spendLimit.limitSource (key or account). Only a signed-in session may loosen an effective cap; an agent key may only tighten one (LOOSEN_REQUIRES_SESSION, 403). A key can read its own effective caps with GET /api/v1/agent/keys/self/limits, which is the check to make before a large trade.

Two things this gate deliberately does not cover: sells (a sell spends the token being sold), and base-pair swaps between SOL, USDC, and CNDL, which are never spend-capped.

A separate, independent dimension is the per-key transaction limit (txLimit): a cumulative USD volume ceiling over a rolling window rather than a per-transaction cap. See transaction limits.

GET /api/v1/agent/limits
{ "success": true, "main": [{ "asset": "sol", "maxPerTxRaw": "2000000000" }], "linked": null }

null means unlimited for that scope.

PUT /api/v1/agent/limits
content-type: application/json
{ "scope": "main", "limits": [{ "asset": "sol", "maxPerTxRaw": "2000000000" }] }

Each limit is { "asset": "sol" | "usdc" | "cndl" | "eth" | "usdg", "maxPerTxRaw": "<raw units>" }. Send limits: null (or []) to clear a scope back to unlimited.

Tightening (a new cap, or a lower one) works from either an agent key or a Privy session. Loosening (raising or clearing a cap) requires a Privy session: an agent key that tries is rejected LOOSEN_REQUIRES_SESSION (403) before anything is written, so a compromised key can never widen its own leash.

See the Agent trading API for how a trade’s total spend, amount plus fee, is checked against the payer’s scope at build time.

staging.candle.tv/dev/agent lists an account’s linked wallets with revoke, and exposes both spend-limit scopes as editors, next to the tier strip.

CodeStatusMeaning
TIER_REQUIRED403Free or Believer account attempted an import, link, self-signed launch, or linked-payer trade.
WALLET_LIMIT_REACHED400The account already has the maximum active linked wallets for its tier; revoke one first.
WALLET_ALREADY_LINKED409The address already has an active linked-wallet row, on this account or another one.
LOOSEN_REQUIRES_SESSION403PUT /api/v1/agent/limits loosened a cap from an agent key instead of a Privy session.