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.
Two ways to add a wallet
Section titled “Two ways to add a wallet”| Path | Grants | Use it for |
|---|---|---|
| Import | A Candle-managed signer and a spend policy, so the agent key can sign with it | Self-signed launches, linked-payer trades |
| Link existing | Attribution only, no signer, no spend authority | Counting 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 a wallet
Section titled “Importing a wallet”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:
POST /api/v1/agent/wallets/import/initreturns Privy’s HPKE receiver public key for the wallet’s chain and address.- 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.
POST /api/v1/agent/wallets/import/submitposts 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": "..." }Linking an existing wallet
Section titled “Linking an existing wallet”POST /api/v1/agent/walletsAttaches 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.
Listing and revoking
Section titled “Listing and revoking”GET /api/v1/agent/walletsLists the account’s linked wallets, active ones first.
DELETE /api/v1/agent/wallets/:idTombstones 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.
Attribution
Section titled “Attribution”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).
| Scope | Meaning |
|---|---|
selected | The profile can spend only from its assigned wallets. This is the default for every profile created since profiles shipped. |
all | The 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 create → wallets import → trade flow needs no extra step.
Reading and setting the set
Section titled “Reading and setting the set”candle keys wallets ck_live_abcd1234candle keys wallets set ck_live_abcd1234 --wallets wal_1,wal_2candle keys wallets scope ck_live_abcd1234 --scope selectedThe 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”| Scope | Notes | |
|---|---|---|
| Wallet access | Per profile | The assignment set above. |
| Per-asset max-per-transaction cap | Per profile, falling back to the account | A 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 profile | Daily, weekly, monthly or lifetime. Independent of the per-asset caps; whichever binds tighter wins. |
| Request rate limit | Per profile | Each key carries its own rateLimitPerMin. |
| Daily launch cap | Both | Each 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 cap | Per account | Set by tier. Assigning a wallet to several profiles does not consume it more than once. |
| Tier and its floors | Per account | A profile cannot exceed what the account’s tier allows. |
| Trading and launch fees | Per account | Charged on the trade, identical whichever profile submitted it. |
| Usage metering | Per profile | Trade volume, trade count and launches are recorded against the acting key, so each profile’s activity is separable. |
| Fee totals | Per account | Accumulated per account and asset. GET /agent/keys/{prefix}/pnl also reports the fees charged against one profile’s own fills. |
| Realized and unrealized P&L | Per profile | Computed 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.
Spend limits
Section titled “Spend limits”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:
| Scope | Governs | Enforced |
|---|---|---|
main | The account’s own delegated wallet | Server-side, when Candle builds a launch dev buy or a trade |
linked | Every spend-capable linked wallet | Server-side at build time, and compiled into each wallet’s own Privy policy |
| Per key | One API key, per asset, via PUT /api/v1/agent/keys/:prefix/limits | Server-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/limitscontent-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.
The wallet manager
Section titled “The wallet manager”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.
Errors
Section titled “Errors”| Code | Status | Meaning |
|---|---|---|
TIER_REQUIRED | 403 | Free or Believer account attempted an import, link, self-signed launch, or linked-payer trade. |
WALLET_LIMIT_REACHED | 400 | The account already has the maximum active linked wallets for its tier; revoke one first. |
WALLET_ALREADY_LINKED | 409 | The address already has an active linked-wallet row, on this account or another one. |
LOOSEN_REQUIRES_SESSION | 403 | PUT /api/v1/agent/limits loosened a cap from an agent key instead of a Privy session. |
Next steps
Section titled “Next steps”- Launch from a linked wallet: Headless launch API
- Trade from a linked wallet: Agent trading API
- The tier a wallet needs, and how to earn it: Agent access & API keys
- A typed client for the import flow: TypeScript SDK