SuperAgent API

Wallets, approvals, and trading

Managed wallets let your app use a wallet address without receiving its private key or recovery phrase. Before money moves, HivemindOS checks the wallet policy, a short-lived quote, the API key’s permissions and limits, available Agent Credits, and any required approval.

Start safely

Use a test network first. Keep proposal, approval, and execution on separate keys. Show the complete action and maximum price before approval.

Move to a main network only after policy, approval separation, retry safety, event handling, and recovery have been tested end to end.

Separate responsibilities

Use different API keys for proposing and approving asset movement:

Responsibility Suggested scopes
Wallet reader wallets:read
Wallet creator wallets:create, wallets:read
Transfer worker wallets:read, wallets:transact
Message signer wallets:read, wallets:sign
Trading worker wallets:read, trading:read, trading:execute
Swap worker wallets:read, wallets:transact
Own-wallet swap builder wallets:read
Reviewer approvals:read, approvals:write
Policy administrator wallets:read, approvals:write

Add managed-wallets, managed-trading, or universal-swaps to each key’s service allowlist as appropriate. A key that can execute an action should not also approve it.

Supported networks

  • base
  • base-sepolia
  • ethereum
  • ethereum-sepolia
  • solana
  • solana-devnet

Availability may vary. Read GET /services and current pricing before offering a network or paid action.

Create a managed wallet

const created = await hive.wallets.create(
  {
    name: "Testnet treasury",
    network: "base-sepolia",
    kind: "agent",
    policy: {
      enabled: true,
      allowedNetworks: ["base-sepolia"],
      allowedAssets: ["ETH"],
      allowedRecipients: ["0x1111111111111111111111111111111111111111"],
      allowedContracts: [],
      maxTransactionUsd: 25,
      maxDailyUsd: 100,
      requireApprovalAboveUsd: 5,
    },
  },
  { idempotencyKey: "testnet-treasury-v1" },
);

if (!created.ok) throw new Error(created.error);
console.log(created.wallet.id, created.wallet.address);

Wallet creation returns an address and policy, not a private key or recovery phrase. Use wallets.list, wallets.get, and wallets.balances for read operations.

The policy controls:

  • whether actions are enabled;
  • allowed networks, assets, recipients, and contracts;
  • maximum value for one transaction;
  • maximum daily transaction value; and
  • the value above which a separate approval is required.

Updating a policy requires approvals:write, an idempotency key, and the exact wallet id. Treat policy administration as a reviewer responsibility.

Transfer flow

1. Request a quote

Amounts are positive atomic-unit integer strings. Do not use floating-point numbers for asset quantities.

const quoted = await worker.wallets.quoteTransaction(
  walletId,
  {
    kind: "send",
    network: "base-sepolia",
    asset: "ETH",
    amount: "1000000000000000",
    recipient: "0x1111111111111111111111111111111111111111",
  },
  { idempotencyKey: "invoice-1042-quote" },
);

if (!quoted.ok) throw new Error(quoted.error);

The quote contains maximumDebitCredits, an estimated network fee when available, expiresAt, approvalRequired, and an approvalId when review is required. Show the exact asset action, visible fee estimate, and maximum HivemindOS credit price to the reviewer.

2. Approve when required

if (quoted.quote.approvalRequired) {
  const reviewed = await reviewer.approvals.decide(
    quoted.quote.approvalId!,
    "approve",
    { idempotencyKey: "invoice-1042-approval" },
  );
  if (!reviewed.ok) throw new Error(reviewed.error);
}

Approval applies only to the exact quoted action and expires with the quote. A rejection cannot be executed.

3. Submit once

const submitted = await worker.wallets.submitTransaction(
  walletId,
  {
    quoteId: quoted.quote.id,
    approvalId: quoted.quote.approvalId ?? undefined,
  },
  { idempotencyKey: "invoice-1042-submit" },
);

if (!submitted.ok) throw new Error(submitted.error);

The same pattern supports kind: "swap" with fromAsset, toAsset, amount, and optional slippageBps. Quotes are short-lived and one-time. If a quote expires, request a new quote and present it for review again.

Message-signing flow

Message signatures always require a matching approval:

const quoted = await signer.wallets.quoteSignature(
  walletId,
  { message: "Sign in to Example at 2026-08-24T12:00:00Z" },
  { idempotencyKey: "example-sign-in-quote-001" },
);

if (!quoted.ok) throw new Error(quoted.error);

await reviewer.approvals.decide(
  quoted.approval.id,
  "approve",
  { idempotencyKey: "example-sign-in-approve-001" },
);

const signed = await signer.wallets.sign(
  walletId,
  { quoteId: quoted.quote.id, approvalId: quoted.approval.id },
  { idempotencyKey: "example-sign-in-execute-001" },
);

Show the reviewer the complete message. Never ask a user to approve an unread or partially hidden signing payload.

Spot-trading flow

Managed trading currently supports market spot orders. Markets use BASE_ASSET/QUOTE_ASSET, such as ETH/USDC.

const quoted = await trader.trading.quote(
  {
    walletId,
    market: "ETH/USDC",
    side: "buy",
    amount: "10000000",
    amountType: "quote",
    orderType: "market",
    slippageBps: 100,
  },
  { idempotencyKey: "rebalance-eth-usdc-quote-001" },
);

if (!quoted.ok) throw new Error(quoted.error);

if (quoted.quote.approvalRequired) {
  await reviewer.approvals.decide(
    quoted.quote.approvalId!,
    "approve",
    { idempotencyKey: "rebalance-eth-usdc-approve-001" },
  );
}

const order = await trader.trading.createOrder(
  {
    quoteId: quoted.quote.id,
    approvalId: quoted.quote.approvalId ?? undefined,
  },
  { idempotencyKey: "rebalance-eth-usdc-submit-001" },
);

Read positions with trading.positions(walletId). A submitted order may still be processing; use its status and signed events instead of treating request acceptance as a confirmed fill.

Copy-trading strategies remain a separate managed service. They do not turn a SuperAgent API spot order into a limit, recurring, leveraged, or copy-trading order.

Universal swaps

Universal swaps turn any token into any other token, on one chain or across chains. Add universal-swaps to the key’s service allowlist. There are two ways to use it:

  • From a HivemindOS wallet: quote, approve if the wallet’s policy asks for it, then execute. HivemindOS signs and sends.
  • From your own wallet: ask for a checked, unsigned route and sign it yourself. HivemindOS never holds the key.
Method and path Scope What it does
GET /v1/swaps/tokens wallets:read Chains you can swap on and the official token contract each symbol means. Optional chainId query.
POST /v1/swaps/quote wallets:transact Quote a swap from a HivemindOS wallet. Needs an Idempotency-Key.
POST /v1/swaps wallets:transact Execute an approved quote. Needs an Idempotency-Key.
GET /v1/swaps wallets:read List swaps.
GET /v1/swaps/{swapId} wallets:read Read one swap, with cross-chain fill status.
POST /v1/swaps/prepare wallets:read Build an unsigned route for your own wallet. No credits.
GET /v1/swaps/routes/{routeId} wallets:read Follow a route your own wallet sent until it fills.

GET /v1/swaps/tokens lists every chain with canSwapFrom and canSwapTo. canSwapFrom is about HivemindOS wallets, which can start a swap on Base, Ethereum, and Solana. Your own wallet can start on any listed chain. A symbol such as usdc resolves only to the official contract listed for that chain. Any other token works when you pass its contract or mint address, and the response flags it as unverified.

Say what to swap

Name the tokens as chain:symbol, such as base:usdc or solana:sol, or as a contract or mint address. Then give one amount:

Field Meaning
amount Decimal amount of from to spend, such as "50".
amountAtomic The same, in the token’s smallest unit.
amountOut Decimal amount of to to receive exactly, such as "0.8".
amountOutAtomic The same, in the token’s smallest unit.

Give an amount to spend or an amount to receive, not both. With amountOut, the route prices what it costs to receive at least that amount, and tradeType comes back as EXACT_OUTPUT. Otherwise it is EXACT_INPUT.

You can also describe the swap in plain words in request and leave the fields out:

  • "swap 50 USDC on Base to SOL"
  • "i need 0.8 sol"
  • "swap usdc to 0.8sol"
  • "move 20 usdc from solana to base"

A short, exact sentence like the first one is read directly. Anything looser is read the same way Ask Scout on the hivemindos.app Trade desk reads it. That uses managed decisions and is billed like any other decision call. A key without services.invoke.managed-models.decisions.create gets HTTP 403 with a clear message; use from, to and an amount instead. When the words name no chain, funds start on defaultChain (a chain slug, default base). When they name nothing to pay with, a HivemindOS wallet pays from what it holds, dollars first.

Every quote and route returns interpretation: the swap as HivemindOS read it. Show it to the person before anything is spent.

Other optional fields: slippageBps (0 to 10,000; the router sets it when omitted), fromWalletId, toWalletId, recipient, and provisionMissingWallets (create a receiving HivemindOS wallet on the destination chain if the account has none there; needs wallets:create).

Swap from a HivemindOS wallet

const quoted = await worker.swaps.quote(
  { from: "base:usdc", to: "solana:sol", amountOut: "0.8" },
  { idempotencyKey: "treasury-sol-quote-001" },
);
if (!quoted.ok) throw new Error(quoted.error);

const { quote } = quoted;
// Show this before approving: what was understood, what it costs, what arrives.
console.log(quote.interpretation, quote.tradeType);
console.log(`Pay ${quote.from.amount} ${quote.from.symbol} on ${quote.from.chainName}, receive ${quote.to.amount} ${quote.to.symbol} on ${quote.to.chainName}`);

if (quote.approvalRequired) {
  await reviewer.approvals.decide(quote.approvalId!, "approve", { idempotencyKey: "treasury-sol-approve-001" });
}

const executed = await worker.swaps.execute(
  { quoteId: quote.id, approvalId: quote.approvalId ?? undefined },
  { idempotencyKey: "treasury-sol-execute-001" },
);
if (!executed.ok) throw new Error(executed.error);

const swap = await worker.swaps.get(executed.swap.id);

Or with curl:

curl https://api.hivemindos.app/v1/swaps/quote \
  -H "Authorization: Bearer $HIVEMINDOS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: treasury-sol-quote-001" \
  -d '{"request":"i need 0.8 sol"}'

curl https://api.hivemindos.app/v1/swaps \
  -H "Authorization: Bearer $HIVEMINDOS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: treasury-sol-execute-001" \
  -d '{"quoteId":"swap_quote_...","approvalId":"approval_..."}'

The quote includes the from and to amounts, minimumOutputAmount, the price impact, estimated dollar values and network fee, the route’s steps, warnings, maximumDebitCredits, expiresAt, approvalRequired, and approvalId. It lasts five minutes. A quote may also create a receiving wallet and report it as walletProvisioned.

Executing fetches a fresh route rather than replaying the old one, then checks it against what was approved. It refuses a route that would deliver less than minimumOutputAmount. For an exact receive amount, it asks for the same amount again and refuses a cost above the approved one plus slippage (1% when you set none). A refused execution spends nothing; ask for a new quote.

An executed swap charges 20 Agent Credits, and only when it runs. A 0.45% platform fee is inside the route’s price. The quote’s maximumDebitCredits is the authoritative charge.

A same-chain swap is done when its transaction lands (status: "broadcast"). A cross-chain swap starts as filling. Read it with GET /v1/swaps/{swapId} until it becomes filled, refunded, or failed; the read includes routeStatus and, once filled, destinationTransactionHash. Once the source transaction confirms, a cross-chain fill cannot be cancelled.

Swap from your own wallet

POST /v1/swaps/prepare returns the route for a wallet HivemindOS does not hold. You sign and send it. Nothing is stored, nothing is spent, and no credits are charged; the 0.45% platform fee is inside the route (feeBps: 45).

Field Meaning
sender Required. The address on the source chain that signs and pays.
recipient Required when the swap arrives on the other kind of chain (EVM and Solana). Otherwise it defaults to sender.
from, to, amount fields, request, slippageBps, defaultChain As above.

Because this route does not read your balance, it refuses “all” or a share of a balance: name an amount to spend or to receive. It can start on any chain the router serves, not only the three HivemindOS wallets start on.

The response:

{
  "ok": true,
  "custody": "self",
  "expiresInSeconds": 30,
  "route": {
    "from": { "symbol": "USDC", "chain": "base", "amount": "50", "amountAtomic": "50000000" },
    "to": { "symbol": "SOL", "chain": "solana", "amount": "0.31", "amountAtomic": "310000000" },
    "sender": "0x…",
    "recipient": "So1…",
    "tradeType": "EXACT_INPUT",
    "crossChain": true,
    "minimumOutputAmount": "306900000",
    "priceImpactPercent": 0.62,
    "estimatedInputUsd": 50,
    "estimatedOutputUsd": 49.69,
    "estimatedNetworkFeeUsd": 0.02,
    "estimatedSeconds": 12,
    "feeBps": 45,
    "routeId": "0x…",
    "interpretation": "50 USDC on Base → SOL on Solana",
    "transactions": [
      { "kind": "evm", "chainId": 8453, "step": "approve", "description": "Approve USDC", "to": "0x…", "data": "0x…", "value": "0" },
      { "kind": "evm", "chainId": 8453, "step": "deposit", "description": "Deposit", "to": "0x…", "data": "0x…", "value": "0" }
    ],
    "warnings": []
  }
}

The values above are illustrative. Sign and send transactions in order, and wait for each to confirm before sending the next. An EVM route is often an approval followed by the swap itself.

HivemindOS checks every transaction before returning it:

  • EVM calls only reach contracts the router publishes, or approve or transfer the token you pay with to one of them, for no more than the amount.
  • A Solana transaction is signed only by sender, calls only the router’s published programs and standard Solana programs, sends SOL only to sender or a published solver, and never approves a delegate.

Prices hold for about 30 seconds. Sign promptly, or prepare again.

EVM wallet

const prepared = await hive.swaps.prepare({
  from: "base:usdc",
  to: "arbitrum:eth",
  amount: "50",
  sender: account,
});
if (!prepared.ok) throw new Error(prepared.error);

const hex = (value: string) => `0x${BigInt(value).toString(16)}`;
for (const tx of prepared.route.transactions) {
  if (tx.kind !== "evm") continue;
  await ethereum.request({ method: "wallet_switchEthereumChain", params: [{ chainId: hex(String(tx.chainId)) }] });
  const hash = await ethereum.request({
    method: "eth_sendTransaction",
    params: [{ from: account, to: tx.to, data: tx.data, value: hex(tx.value), ...(tx.gas ? { gas: hex(tx.gas) } : {}) }],
  });
  await waitForReceipt(hash); // your provider's receipt wait
}

Solana wallet

import { Connection, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js";

const prepared = await hive.swaps.prepare({
  request: "swap 2 sol to usdc on base",
  sender: wallet.publicKey.toBase58(),
  recipient: "0x1111111111111111111111111111111111111111",
});
if (!prepared.ok) throw new Error(prepared.error);

const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
for (const tx of prepared.route.transactions) {
  if (tx.kind !== "svm") continue;
  let transaction: VersionedTransaction;
  if (tx.transaction) {
    transaction = VersionedTransaction.deserialize(Buffer.from(tx.transaction, "base64"));
  } else {
    // Solana couldn't be reached when the route was built: rebuild it with a fresh blockhash.
    const tables = await Promise.all(tx.addressLookupTableAddresses.map(async (address) =>
      (await connection.getAddressLookupTable(new PublicKey(address))).value!));
    const { blockhash } = await connection.getLatestBlockhash("confirmed");
    const instructions = (tx.instructions as any[]).map((ix) => new TransactionInstruction({
      programId: new PublicKey(ix.programId),
      keys: ix.keys.map((key: any) => ({ pubkey: new PublicKey(key.pubkey), isSigner: key.isSigner, isWritable: key.isWritable })),
      data: Buffer.from(ix.data, "hex"), // instruction data is hex, not base64
    }));
    transaction = new VersionedTransaction(new TransactionMessage({
      payerKey: wallet.publicKey,
      recentBlockhash: blockhash,
      instructions,
    }).compileToV0Message(tables));
  }
  const signed = await wallet.signTransaction(transaction);
  const signature = await connection.sendRawTransaction(signed.serialize());
  await connection.confirmTransaction(signature, "confirmed");
}

Follow the fill

A same-chain route is done when its last transaction confirms. For a cross-chain route, poll with the routeId:

curl https://api.hivemindos.app/v1/swaps/routes/$ROUTE_ID \
  -H "Authorization: Bearer $HIVEMINDOS_API_KEY"
# {"ok":true,"routeId":"0x…","status":"success","filled":true,"destinationTransactionHash":"…"}
const fill = await hive.swaps.routeStatus(prepared.route.routeId!);
if (fill.ok && fill.filled) console.log(fill.destinationTransactionHash);

A key made from an Agent Credit token cannot hold wallets:transact, so it cannot quote or execute swaps from HivemindOS wallets. It can use POST /v1/swaps/prepare and GET /v1/swaps/routes/{routeId} with wallets:read.

Credit and asset boundaries

  • HivemindOS credits pay for wallet creation, execution, signing, managed trading, and executed swaps from HivemindOS wallets.
  • Wallet assets pay the transfer or trade amount and its network fee.
  • The server-owned pricing response defines the maximum HivemindOS credit charge.
  • Wallet policy defines the permitted asset action.
  • creditsPerDay can add a key-level daily ceiling to wallet creation, transaction execution, signature execution, trading-order execution, and swap execution.

HTTP 402 means the HivemindOS account balance is too low for the managed API charge. It does not mean the wallet has enough assets for the transfer or trade. Check both balances separately and present the appropriate next action.

Next: set endpoint and daily credit limits, then verify signed events.

Expanded image Scroll to pan · Esc to close
100%