SDK Wallet Actions
GET /agent/job/{jobId} returns neither transactions nor actions: prompts sent with an API key execute from your Bankr wallet, and the job's response and richData describe what ran. Wallet actions come back from the x402 Bankr SDK (@bankr/sdk), whose job status (GET /v2/job/{jobId}) prepares on-chain work for a wallet you control to sign.
@bankr/sdk 0.1.0-alpha.10 replaces JobStatus.transactions and the per-type transaction objects (swap, approval, transfer_erc20, avantisTrade, ...) with JobStatus.actions.
Shape
A completed job that prepared on-chain work returns actions, a list of PreparedAction. Swaps, transfers, wraps, NFTs, Avantis trades, bridges and staking all use this one shape, so a client needs no per-tool handling: it runs what each step says, on the chain it names, in the order given.
interface PreparedAction {
/** Stable per action, so clients can key and report on it. */
id: string;
/** What running every step does, e.g. "Swap 10 USDC for ETH on Base". */
description: string;
steps: WalletStep[];
}
type WalletStep = SendTransactionStep | SignTypedDataStep;
interface SendTransactionStep {
kind: "sendTransaction";
/** An approval only unlocks a later step. */
role: "approval" | "action";
chainId: number;
to: `0x${string}`;
data?: `0x${string}`;
/** Wei, hex. */
value?: `0x${string}`;
/** Pinned only where the builder priced this exact calldata (a 0x swap). */
gas?: string;
gasPrice?: string;
description: string;
}
interface SignTypedDataStep {
kind: "signTypedData";
chainId: number;
/** EIP-712 payload, as eth_signTypedData_v4 takes it. */
typedData: {
domain: Record<string, unknown>;
types: Record<string, { name: string; type: string }[]>;
primaryType: string;
message: Record<string, unknown>;
};
use: "appendToNextStep";
description: string;
}
Execution Rules
- Actions are independent. Each one is a complete piece of work; run them one after another.
- Steps run in order. Wait for each
sendTransactionstep to confirm on-chain before running the next step. A step that reverts stops the action. - Sign with the context wallet. The steps are built for the
walletAddressthe prompt was sent for, on thechainIdeach step names. - Signatures go on the next send. A
signTypedDatastep signs itstypedData; the signature is then appended to the nextsendTransactionstep's calldata asconcat(data, uint256 signature length (32 bytes), signature). This is what a 0x Permit2 swap expects. role: "approval"marks a token approval that only unlocks a later step. It still has to be sent and confirmed.
Example
A USDC-to-ETH swap on Base: an approval, a Permit2 signature, then the swap carrying that signature.
{
"status": "completed",
"response": "Ready to swap 10 USDC for ETH on Base...",
"actions": [
{
"id": "...",
"description": "Swap 10 USDC for ETH on Base",
"steps": [
{
"kind": "sendTransaction",
"role": "approval",
"chainId": 8453,
"to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"data": "0x095ea7b3...",
"description": "Approve USDC"
},
{
"kind": "signTypedData",
"chainId": 8453,
"typedData": {
"domain": { "...": "..." },
"types": { "...": [] },
"primaryType": "PermitTransferFrom",
"message": { "...": "..." }
},
"use": "appendToNextStep",
"description": "Sign the Permit2 transfer"
},
{
"kind": "sendTransaction",
"role": "action",
"chainId": 8453,
"to": "0x...",
"data": "0x...",
"value": "0x0",
"gas": "300000",
"gasPrice": "10000000",
"description": "Swap 10 USDC for ETH"
}
]
}
]
}
A plain transfer is one action with a single sendTransaction step; a BNKR stake can be an approval step followed by the stake.
Running Actions
With viem, for steps on Base:
import {
concat,
createPublicClient,
createWalletClient,
hexToBigInt,
http,
numberToHex,
size,
type Hex,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { base } from 'viem/chains';
import type { PreparedAction } from '@bankr/sdk';
const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as Hex);
const wallet = createWalletClient({ account, chain: base, transport: http() });
const publicClient = createPublicClient({ chain: base, transport: http() });
async function runAction(action: PreparedAction) {
let signature: Hex | undefined;
for (const step of action.steps) {
if (step.chainId !== base.id) {
throw new Error(`"${step.description}" runs on chain ${step.chainId}`);
}
if (step.kind === 'signTypedData') {
signature = await wallet.signTypedData(
step.typedData as Parameters<typeof wallet.signTypedData>[0],
);
continue;
}
// Append the previous step's signature: uint256 length, then the bytes.
const data = signature
? concat([step.data ?? '0x', numberToHex(size(signature), { size: 32 }), signature])
: step.data;
signature = undefined;
const hash = await wallet.sendTransaction({
to: step.to,
data,
value: step.value ? hexToBigInt(step.value) : undefined,
gas: step.gas ? BigInt(step.gas) : undefined,
gasPrice: step.gasPrice ? BigInt(step.gasPrice) : undefined,
});
// Each send confirms before the next step runs.
const receipt = await publicClient.waitForTransactionReceipt({ hash });
if (receipt.status !== 'success') {
throw new Error(`"${step.description}" reverted: ${hash}`);
}
}
}
const result = await client.promptAndWait({ prompt: 'Swap 10 USDC for ETH on Base' });
for (const action of result.actions ?? []) {
await runAction(action);
}