Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Signing & meta-transactions · Boson Protocol
Skip to content

Signing & meta-transactions

Every state-changing call to Boson is signed. There are five distinct signing flows; this page is the canonical reference for all of them. Anywhere else in the docs that mentions signing links here, not to a duplicate explainer.

At a glance

FlowWho signsWho pays gasWhere it's used
Direct on-chainThe user/callerThe user/callerDefault for SDK and send_signed_transaction
Boson meta-tx (Biconomy)The userA relayerGas-less UX for users
Token-auth meta-txThe user (+ token sig)A relayerGas-less UX for users
FullOffer EIP-712The offer creator(varies)Non-listed (private) offers, x402
Mutual dispute resolutionBuyer + seller (both)Whoever submitsresolveDispute

Direct on-chain

You sign and broadcast yourself. The SDK does this transparently when you call sdk.commitToOffer(...) etc.

For MCP-driven agents, the 3-step pattern:

  1. Call the tool (e.g. commit_to_offer) → returns an unsigned { to, data, value, gasLimit, chainId }.
  2. Sign locally with your wallet.
  3. Broadcast via send_signed_transaction or your own RPC.
const unsigned = await mcp.commitToOffer({ /* … */ })
const signed = await wallet.signTransaction(unsigned)
await mcp.sendSignedTransaction({ chainId: 84532, signedTx: signed })

See Build for AI agents → The 3-step signing pattern for the agent-specific deep dive.

Boson meta-tx (Biconomy)

The user signs an EIP-712 envelope; a relayer (Biconomy or your own) calls MetaTransactionsHandlerFacet.executeMetaTransaction and pays gas.

// Sign on the client:
const signedMetaTx = await sdk.metaTx.signMetaTxCommitToOffer({
  buyer: userAddress,
  offerId,
})
 
// Relay (either via Biconomy directly, or your own forwarder):
await sdk.metaTx.relayMetaTransaction(signedMetaTx)

Or via MCP:

send_meta_transaction { chainId, functionName, functionSignature, userAddress, nonce, signature }

Token-auth meta-tx

Th user can pay the protocol in a single signed envelope, without prior approve. Three variants:

  • ERC-3009receiveWithAuthorization (used by USDC, EURC, others).
  • EIP-2612 Permit — for tokens that implement permit.
  • Permit2 — the Uniswap Permit2 variant.

The relayed call is executeMetaTransactionWithTokenTransferAuthorization. See Reference → Contracts → Diamond facets → MetaTransactionsHandlerFacet.

Via MCP:

send_forwarded_meta_transaction {
  chainId, functionName, functionSignature, userAddress, nonce, signature,
  tokenAuth: { type: "ERC3009" | "Permit" | "Permit2", … }
}

FullOffer EIP-712

A FullOffer is a seller-signed payload representing an offer that hasn't been written on-chain. Used for non-listed (private) offers and for the x402 stack.

const fullOffer = await sdk.signFullOffer({ /* offer params */ })
// fullOffer.signature, fullOffer.offerHash, …
 
// Later, atomic commit:
await sdk.createOfferAndCommit({ fullOffer })

The signature can be verified on-chain (EOA or EIP-1271 smart account). The hash, once committed, is recorded as used so it can't be re-played.

Mutual dispute resolution

resolveDispute requires both buyer and seller to sign the same payload (offering buyerPercent). Whichever party submits the on-chain call attaches both signatures.

See Build → Buyers → Raise a dispute and Recipes → Mutual dispute resolution.

Nonce management

Each user has a per-chain nonce in the Diamond's MetaTransactionsHandlerFacet. Read with isUsedNonce(address, nonce). The SDK reads + bumps for you; agents using raw MCP must manage their own.

Common footguns

  • Mismatched EIP-712 typed-data. The SDK builds the payload identically to the on-chain verifier. Don't hand-build typed-data; always use sdk.metaTx.signMetaTx* or the MCP sign_* tools.
  • Stale nonces under concurrency. If two transactions for the same user are signed in parallel with the same nonce, one will fail. Serialize or use distinct nonces.
  • Permit2 has a different domain separator than EIP-2612. Don't mix them.
  • signTypedData UI varies wildly across wallets. Test with MetaMask, Coinbase Wallet, and a hardware wallet before assuming any signed-data flow works.

Where to look in code

Next