Live wallet-funded onboarding

Exactly three AgentBroker requests take a fresh non-custodial Solana agent to its first wallet-funded trade. You keep the wallet and private key; AgentBroker verifies finalized mainnet USDC, builds one unsigned Jupiter transaction, and records the settlement.

Required actions: (1) register with the signer public key, (2) fund externally and submit the finalized USDC signature plus trade intent once, (3) sign and broadcast locally, wait for finality, and submit one confirmation request. Local Solana work is mandatory but is not another AgentBroker request.

Important: AgentBroker never supplies a platform deposit address and never receives private key material. Send mainnet USDC to the wallet you register, sign locally, and submit only public transaction signatures to the API.

The contracts used by this walkthrough

MethodEndpointPurpose
POST/v1/onboarding/first-trade/registerAction 1: register the signer public key and receive the one-time API key.
GET/api/v1/agents/meOptional evidence: read wallet state and real balance.
POST/api/v1/account/depositCompatibility/reference evidence only; action 2 performs this verification inline.
GET/v1/depositsOptional evidence: read deposit status and confirmations.
GET/v1/accountOptional evidence: read the agent-visible funding result.
POST/v1/onboarding/first-trade/fund-and-prepareAction 2: verify finalized mainnet USDC and return one unsigned Jupiter transaction for the intent.
POST/v1/onboarding/first-trade/confirmAction 3: record the locally signed and finalized settlement signature.
GET/v1/balance/historyOptional evidence: read balance events.
GET/v1/feesOptional reference: separate order-book fee schedule, not the Jupiter fee response.

1 Register and save the API key

Generate or load a Solana keypair in your own signer, send only its public key, and save the returned API key immediately. It cannot be retrieved again.

POST /v1/onboarding/first-trade/register
cURL
BASE_URL="https://agentbroker.polsia.app"
WALLET_PUBLIC_KEY="YOUR_SOLANA_PUBLIC_KEY"

curl -sS -X POST "$BASE_URL/v1/onboarding/first-trade/register" \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"my-live-agent\",\"solana_wallet_address\":\"$WALLET_PUBLIC_KEY\"}"

The 201 response includes agent_id, solana_wallet_address, registration_mode: "non_custodial", and api_key. Store the key in a secret manager and send it as X-API-Key: <api_key> on every authenticated request below.

Response shape
{
  "agent_id": 42,
  "solana_wallet_address": "YOUR_SOLANA_PUBLIC_KEY",
  "registration_mode": "non_custodial",
  "balance_usdc": "0.000000",
  "api_key": "ab_..."
}

Optional evidence check (not a required action)

GET /api/v1/agents/me
cURL
curl -sS "$BASE_URL/api/v1/agents/me" \
  -H "X-API-Key: $API_KEY"

Before a verified deposit, expect real_mode_active: false and a zero balance_usdc. Activation is computed only when the wallet is verified, the finalized mainnet deposit is recorded, and the real balance is positive.

2 Fund externally and prepare the first trade

Use the Solana mainnet USDC mint EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v. Send USDC to the exact solana_wallet_address returned at registration. Keep enough native SOL in that same wallet to pay Solana transaction fees for the later Jupiter swap and confirmation.

There is no AgentBroker deposit address. The connected wallet is yours. Confirm the recipient and token mint in your wallet before signing the transfer, then wait for Solana finality and keep its transaction signature.

Optional evidence: standalone deposit verification

Submit the transfer signature to the canonical verified-deposit endpoint. The verifier observes the amount on-chain; omit amount unless you want to provide an assertion. A response with HTTP 201 is new, while HTTP 200 is an idempotent replay of the same confirmed deposit.

POST /api/v1/account/deposit
Request
curl -sS -X POST "$BASE_URL/api/v1/account/deposit" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tx_hash":"FINALIZED_SOLANA_TRANSFER_SIGNATURE"}'

Finality is verified inside action 2

Send the finalized signature and trade intent once to /v1/onboarding/first-trade/fund-and-prepare. Retry only an explicit TRANSACTION_UNFINALIZED response; deposit history and profile reads are optional evidence checks, not required onboarding requests.

Optional standalone deposit evidence reference
JavaScript — executable flow
const BASE_URL = "https://agentbroker.polsia.app";
let apiKey = process.env.AGENTBROKER_API_KEY;
const depositSignature = process.env.DEPOSIT_SIGNATURE;
const headers = () => ({ "X-API-Key": apiKey, "Content-Type": "application/json" });
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function api(path, options = {}) {
  const response = await fetch(`${BASE_URL}${path}`, {
    ...options,
    headers: { ...headers(), ...(options.headers || {}) },
  });
  const body = await response.json();
  return { response, body };
}

async function submitFinalizedDeposit(signature) {
  for (let attempt = 1; attempt <= 12; attempt += 1) {
    const { response, body } = await api("/api/v1/account/deposit", {
      method: "POST",
      body: JSON.stringify({ tx_hash: signature }),
    });
    const code = body.error?.code;
    if ((response.status === 200 || response.status === 201) && body.status === "confirmed") {
      return body;
    }
    if (response.status !== 409 || code !== "TRANSACTION_UNFINALIZED") {
      throw new Error(`deposit verification failed: HTTP ${response.status} ${code || body.message || "unknown error"}`);
    }
    await sleep(5000);
  }
  throw new Error("deposit did not reach finality within the bounded retry window");
}

const confirmedDeposit = await submitFinalizedDeposit(depositSignature);
let activation;
for (let attempt = 1; attempt <= 12; attempt += 1) {
  const history = await api("/v1/deposits?status=confirmed&limit=10");
  const profile = await api("/api/v1/agents/me");
  const deposit = (history.body.deposits || []).find((item) =>
    (item.tx_signature || item.tx_hash) === depositSignature && item.status === "confirmed"
  );
  const positiveBalance = Number(profile.body.balance_usdc) > 0;
  const enoughConfirmations = deposit &&
    Number(deposit.confirmations_received) >= Number(deposit.confirmations_required);
  if (deposit && positiveBalance && enoughConfirmations && profile.body.real_mode_active === true) {
    activation = { deposit, profile: profile.body };
    break;
  }
  await sleep(5000);
}
if (!activation) throw new Error("real_mode_active did not become true within the bounded poll window");
const account = await api("/v1/account");
if (Number(account.body.balance_usdc) <= 0 || Number(account.body.real_balance) <= 0) {
  throw new Error("GET /v1/account did not report a positive real balance");
}
console.log({
  observed_amount_usdc: activation.deposit.amount_usdc,
  confirmations_received: activation.deposit.confirmations_received,
  confirmations_required: activation.deposit.confirmations_required,
  balance_usdc: account.body.balance_usdc,
  real_mode_active: activation.profile.real_mode_active,
});

Action 2 proof: retain the positive finalized deposit_proof, trade_id, unsigned transaction, lane, and Jupiter fee_bps: 10. No activation or polling request is required.

3 Sign locally, broadcast, and confirm one live swap

Action 2 returns a versioned Jupiter transaction for this small bluechip SOL/USDC trade. Decode and sign it with the integrator’s own wallet, submit it to Solana mainnet, wait for finalized confirmation, then post only the resulting signature as action 3.

POST /v1/onboarding/first-trade/fund-and-prepare
Request
const swap = await api("/v1/onboarding/first-trade/fund-and-prepare", {
  method: "POST",
  body: JSON.stringify({
    base_symbol: "SOL",
    quote_symbol: "USDC",
    side: "buy",
    quantity: 0.05,
    lane: "bluechip",
  }),
});
if (!Number.isInteger(Number(swap.body.trade_id)) || !swap.body.unsigned_transaction) throw new Error("invalid unsigned swap response");
if (swap.body.lane_requested !== "bluechip" || swap.body.lane_effective !== "bluechip") throw new Error("swap lane was not bluechip");
if (Number(swap.body.fee_bps) !== 10 || swap.body.platform_fee_rate !== "0.1%") throw new Error("unexpected Jupiter fee rate");
const expectedFee = (BigInt(swap.body.expected_output_amount) * 10n) / 10000n;
if (BigInt(swap.body.platform_fee_amount) !== expectedFee) throw new Error("platform fee amount does not match 10 bps");

The validated fields are trade_id, unsigned_transaction, bluechip lane, fee_bps: 10, platform_fee_rate: "0.1%", and platform_fee_amount. The fee amount is in the output token’s atomic units; keep it with the trade evidence.

Sign locally, submit once, and confirm

JavaScript — local signer
import { Connection, VersionedTransaction } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
const transaction = VersionedTransaction.deserialize(
  Buffer.from(swap.body.unsigned_transaction, "base64")
);
const wallet = loadSignerFromYourLocalKeyStore();
transaction.sign([wallet]);
const settlementSignature = await connection.sendRawTransaction(transaction.serialize());
await connection.confirmTransaction(settlementSignature, "confirmed");

const confirmation = await api("/v1/onboarding/first-trade/confirm", {
  method: "POST",
  body: JSON.stringify({
    trade_id: Number(swap.body.trade_id),
    on_chain_signature: settlementSignature,
  }),
});
if (confirmation.response.status !== 200 || confirmation.body.confirmed_on_chain !== true) {
  throw new Error(`settlement failed: HTTP ${confirmation.response.status}`);
}
if (confirmation.body.on_chain_signature !== settlementSignature || confirmation.body.lane !== "bluechip") {
  throw new Error("settlement evidence does not match the submitted signature and lane");
}
if (!(Number(confirmation.body.fee_usdc) > 0) || !confirmation.body.solscan_link) {
  throw new Error("settlement did not return a positive fee_usdc and Solscan link");
}
console.log({
  trade_id: confirmation.body.trade_id,
  settlement_signature: confirmation.body.on_chain_signature,
  lane: confirmation.body.lane,
  fee_usdc: confirmation.body.fee_usdc,
  solscan_link: confirmation.body.solscan_link,
});

Private key boundary: loadSignerFromYourLocalKeyStore() is your implementation. Never put a seed phrase, private key, or signed transaction secret in an AgentBroker request. If the unsigned transaction expires before broadcast, request a fresh unsigned swap and do not reuse the expired payload.

Retrying the same trade_id with the same settlement signature is idempotent and returns the recorded confirmation. A different signature for an already-confirmed trade is rejected with HTTP 409.

Optional evidence: verify the funding and fee ledger

Keep one evidence record for the run: the deposit signature, the confirmed deposit amount and confirmation counts, the swap trade_id, the settlement signature, the bluechip lane, and returned fee_usdc.

Operator SQL checks
SELECT unsigned_trade_id, on_chain_signature, lane, fee_usdc
FROM confirmed_trades
WHERE unsigned_trade_id = :trade_id
  AND on_chain_signature = :settlement_signature;

SELECT swap_id, tx_signature, lane, fee_usd
FROM platform_fees
WHERE swap_id = :trade_id
  AND tx_signature = :settlement_signature;

POST /v1/trades/swap-unsigned and swap-confirm expose the non-custodial Jupiter fee fields for this settlement. The separate GET /v1/fees order-book schedule is not a promise about the Jupiter platform fee rate; use the swap response and the operator reconciliation for this flow.

Error checklist

SignalWhat to check
WALLET_NOT_CONNECTEDRegister with the signer’s Solana public key and send USDC to that exact connected wallet.
TRANSACTION_UNFINALIZED / 409Wait, then retry the same deposit signature within the bounded loop. Do not submit a second transfer.
TRANSACTION_FAILEDThe transfer did not settle. Do not treat it as funding; inspect the Solana explorer and make a new valid transfer if needed.
REAL_MODE_REQUIREDRe-read /api/v1/agents/me; require verified wallet, finalized deposit, positive balance, and real_mode_active: true before building a swap.
Insufficient SOLLeave native SOL in the connected wallet for transaction fees, then request a fresh unsigned transaction.
Expired unsigned transactionBuild a new swap; never sign or broadcast an expired payload.
Failed settlementInspect the returned confirmation error and Solana signature status. Confirm only the signature actually finalized on-chain.