Skip to content

Arete documentation

Actions

For the complete documentation index optimized for AI agents, see llms.txt or llms-full.txt. A markdown version of this page is available by appending .md to the URL or sending Accept: text/markdown.

For AI agents: the documentation index is at llms.txt (full corpus: llms-full.txt). A markdown source for this page is /sandboxes/actions.md.

An agent changes a sandbox’s wallet through exactly one tool, sandbox_invoke, with one of two actions: invoke or withdraw. Each needs a grant, and each can be previewed first with sandbox_preview, which takes the same arguments.

The loop is: discover → preview → invoke.

  1. sandbox_status and sandbox_rules: what the wallet holds, which actions this agent holds, and which programs and instructions are allowed.
  2. sandbox_derive, when the action needs an address: a PDA (computed locally, with no network call) or an associated token account.
  3. sandbox_preview: a dry run. Returns pass with what the wallet would lose and the transaction it built, or the refusal.
  4. sandbox_invoke: runs it.

Pass u64 amounts as decimal strings.

Named instructions of the programs your rules enable, encoded from each program’s IDL. One to 32 instructions per action, named exactly as sandbox_rules lists them, as long as they fit one transaction: 4096 bytes and 64 accounts for v1 (the default), 1232 bytes for v0. A flow that needs its setup, refreshes and the action together, such as a first lending deposit, goes in one action. The sandbox wallet is the only signer.

{
"action": "invoke",
"args": {
"instructions": [
{
"program": "Pump.fun",
"instruction": "buy",
"args": { "...": "as sandbox_rules lists them" },
"accounts": { "...": "only those without a fill" }
}
],
"computeUnitPriceMicroLamports": "1000"
}
}
ArgumentMeaning
instructions[]{ program, instruction, args?, accounts?, remainingAccounts? }
remainingAccounts[{ address, writable? }], for instructions that take a variable account list
computeUnitLimitOptional compute budget
computeUnitPriceMicroLamportsOptional priority fee, per compute unit
transactionFormat"v1" (default) or "v0"

Accounts with a fill in sandbox_rules can be left out: Arete fills fixed addresses, the wallet, PDAs, token programs, your associated token accounts and fields it reads from the accounts you give (a pool’s mints and vaults). A request still missing accounts is refused with sandbox-intent-invalid, missingAccounts, and accountGuidance that says how to find each one. See Finding accounts.

There is no separate swap action. A swap is an invoke of the instructions that make it, so it passes the same rules, limits and checks as any other instruction:

  1. Resolve the venue.
    • A venue’s own instructions, when you know the pool: for example PumpSwap (pump-amm) buy and sell for a coin that has left its Pump.fun bonding curve, Pump.fun buy and sell on the curve, or a DEX’s own swap instruction (Orca Whirlpool, Raydium, Meteora). This is the simplest path.
    • A Jupiter route, when you want the best route across venues: quote with Jupiter’s Swap API, then ask it for /swap-instructions with the sandbox wallet as the user. Ask the quote for about 50 maxAccounts, so the route fits a v1 transaction (64 accounts, no lookup tables). The route only fills through venues whose programs your rules enable.
  2. Write the invoke. Name each instruction as sandbox_rules lists it (Jupiter’s swap is one of the Jupiter instructions there, such as route_v2 or shared_accounts_route_v2), with its args decoded from the instruction data by the IDL types sandbox_rules gives, its named accounts, and any further accounts the route passes in remainingAccounts. Setup Jupiter returns, such as creating a token account or wrapping SOL, is baseline plumbing: put it in the same action, in order.
  3. sandbox_preview with action: "invoke" until it passes, then sandbox_invoke with the same arguments.
{
"action": "invoke",
"args": {
"instructions": [
{
"program": "PumpSwap",
"instruction": "sell",
"args": { "...": "as sandbox_rules lists them" },
"accounts": { "...": "only those without a fill" }
}
]
}
}

Tokens the sandbox bought may always be sold, through any route the rules allow.

SOL to your wallet, the only destination a withdraw can have.

{ "action": "withdraw", "args": { "lamports": "500000000" } }

Actions build Solana v1 transactions (SIMD-0385) by default: up to 4096 bytes and 64 accounts, no address lookup tables, with the compute budget in the message itself. transactionFormat: "v0" builds the older format instead (up to 1232 bytes). A transaction too large for its format is refused with rule transaction-size.

Every sandbox_invoke has an idempotency key: the agent’s own (idempotencyKey, 1 to 128 characters) or a generated one, returned on success and on error. A key never runs twice.

  • The same request, the same key: safe to send again. It returns the original result, success or refusal, in the same shape plus replayed: true. An action still in flight returns its current status. A replayed refusal also carries the record’s rejectCode, which can differ from its code: go by the replayed code and retryable. A replayed outage is retryable: true, not a permanent refusal; retry it with a new key. See Errors and refusals.
  • A different request, a used key: refused, not retryable, with the id of the action that has the key (sandbox-duplicate-action, or sandbox-idempotency-key-reused). Use a new key.

After a timeout, the action may have run, so never retry it under a new key. Retry with the same key, or look it up first:

  1. sandbox_action with the idempotencyKey.
  2. If it ran, don’t repeat it. If it was refused, retry with a new key. If no action has the key, retry with the same key.

Older servers answer every repeat of a key, the same request included, with sandbox-duplicate-action and the action’s id; look it up with sandbox_action for its result.

A landed action returns its id, status, signature and outflowLamports: everything that left the wallet, the network fee included. networkFeeLamports is the network fee alone, null until the landed transaction is read. feeLamports, when present, is something else: the protocol’s own fees. It also names the guardPipeline that checked it, such as execute-v1. sandbox_action lookups and history items carry outflowLamports, networkFeeLamports and guardPipeline too (not feeLamports), and previews carry outflowLamports and guardPipeline. guardPipeline is null or absent on actions recorded before it was kept and on actions that never reached the guard, such as a rejected one. The full record (decoded instructions, events, balance changes) is in the history. A refusal is a tool error with code, rule, what triggered it, retryable and a hint; see Errors and refusals.