The 3-step signing pattern
State-changing MCP tools never sign for you. They return an unsigned transaction; your code signs and broadcasts. This pattern keeps private keys out of the MCP server.
For the canonical signing reference (typed-data shapes, meta-tx variants), see Concepts → Signing & meta-transactions. This page is the agent-specific operational view.
The three steps
1. Prepare — MCP tool returns { to, data, value, gasLimit, chainId }
2. Sign — your wallet signs the raw tx
3. Broadcast — send via send_signed_transaction (or your own RPC)// 1. Prepare
const { unsignedTx } = await mcp.commitToOffer({
configId: "production-137-0",
signerAddress: wallet.address,
buyer: wallet.address,
offerId,
})
// 2. Sign
const signed = await wallet.signTransaction(unsignedTx)
// 3. Broadcast
const { txHash } = await mcp.sendSignedTransaction({
chainId: 137,
signedTx: signed,
})
// (optional 4) Wait for indexing before reading
await mcp.waitForIndexing({ configId: "production-137-0", blockNumber: receipt.blockNumber })Meta-transaction variant
If you want a relayer to pay gas instead of the agent:
// 1. Sign the inner meta-tx envelope
const { signedMetaTx } = await mcp.signMetaTransaction({
configId,
functionName: "commitToOffer",
functionArgs: { buyer, offerId },
userAddress: wallet.address,
})
// 2. Submit through the relayer
await mcp.sendMetaTransaction(signedMetaTx)Variants:
send_meta_transaction— Boson-generic, via Biconomy.send_native_meta_transaction— direct on-chain EIP-712 (no relayer).send_forwarded_meta_transaction— token-auth meta-tx (single sig covers approve + call).
Pick by your gas-economics and the buyer's UX. See Concepts → Signing.
Per-tool signing requirements
| Tool category | Returns unsigned? | Signing required? |
|---|---|---|
get_* (reads) | n/a | No |
create_seller, update_seller, create_buyer | Yes | Yes |
create_offer, void_offer, … | Yes | Yes |
commit_to_offer, redeem_voucher, cancel_voucher, … | Yes | Yes |
raise_dispute, resolve_dispute, … | Yes | Yes |
deposit_funds, withdraw_funds | Yes | Yes |
sign_full_offer | Returns a signed FullOffer (server signs the typed-data structure) | No, but you must trust the server |
validate_metadata, render_contractual_agreement | n/a | No |
Common gotchas
chainIdmismatch. The unsigned tx specifies a chain. Sign with a wallet whose nonce / chain matches. Otherwise the broadcast reverts.- Gas limit too tight. The MCP returns an estimated
gasLimit; bump 20–30 % if you've seen recent reverts on full estimates. - Don't reuse nonces. If two MCP
commitToOffercalls happen in parallel without nonce coordination, one fails. Serialize or assign nonces yourself.