agentleFS
1Shot-API1Shot-API/skills/public-relayer/SKILL.md

public-relayer

Skill1 starsChanged 7 days agoMITView on GitHub

Integrate a client app with the 1Shot public relayer JSON-RPC API to submit gas-abstracted EIP-7710 delegated transactions on EVM chains. Use when the host or backend calls relayer_* methods directly, redeems a delegation context from wallet_requestExecutionPermissions (embedded 1Shot Wallet or MetaMask), verifies relayer webhooks, builds MetaMask/local-signer delegation flows, or redeems user grants with an agent-wallet EOA (redelegate + estimate/send, including batch executions and multichain). Do NOT use for embedding wallet.1shotapi.com alone (OWSProxy, configure, eth_requestAccounts, eth_sendTransaction) — the wallet calls the relayer internally; use the 1shot-wallet skill instead. Trigger on relayer_send7710Transaction, relayer_estimate7710Transaction, relayer_getCapabilities, relayer_getFeeData, relayer_getStatus, EIP-7710/7715 delegations, requestExecutionPermissions, decodeDelegations, agent-wallet redelegate/auth7702, or gas-abstracted txs via a third-party relay.

The file

Show its rules and text

We split this skill into the rules it gives an agent

22 rules. About 0 are common, 4 are distinctive, and 18 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 18

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    When the user is signing in a browser with MetaMask (or another extension wallet), prefer an extension-first flow:

    line 108
  2. Very specific:

    Create a wallet client from window.ethereum with createWalletClient({ transport: custom(window.ethereum) }).

    line 110
  3. Very specific:

    If requestExecutionPermissions is unavailable, surface this clearly: the connected wallet likely does not support EIP-7715 and the app …

    line 123
  4. Distinctive:

    Follow these steps in order for any new integration.

    line 127
  5. Distinctive:

    Call once per chain you want to support.

    line 131
  6. Distinctive:

    Before quoting or submitting, assemble the full ERC-7710 bundle:

    line 156
  7. Very specific:

    If success === false, fix the bundle from error (missing payment, below min fee, simulation revert, invalid delegation).

    line 191
  8. Very specific:

    If requiredPaymentAmount differs from your mock fee, update the fee execution amount and delegation maxAmount, then re-sign the delegation.

    line 192
  9. Very specific:

    Never do BigInt(feeData.minFee) when minFee is "0.01" — that throws.

    line 205
  10. Very specific:

    Use when the bundle is not built yet — for example, browser permission UX that needs a rough fee before the user signs, or a quick price …

    line 209
  11. Very specific:

    If destinationUrl is set, prefer the webhook — the relayer POSTs signed JSON on each status change.

    line 252
  12. Very specific:

    Look up the public key by kid from the webhook body's keyId.

    line 265
  13. Very specific:

    Verify with Ed25519 over UTF-8 bytes.

    line 267
  14. Very specific:

    If polling is unavoidable, call relayer_getStatus with { id: <TaskId>, logs: true|false } every 2–3 seconds and stop on a terminal status.

    line 269
  15. Very specific:

    Quote fee how?: when the signed bundle exists, prefer relayer_estimate7710Transaction (single-chain) or …

    line 281
  16. Very specific:

    Working directory: run agent-wallet as node scripts/aw.mjs … from the agent-wallet skill root (the directory that contains scripts/), or …

    line 283
  17. Very specific:

    Salt: always pass a fresh random 32-byte hex salt to createDelegation to avoid replay collisions.

    line 290
  18. Very specific:

    Arc mainnet (5042): call registerSmartAccountsEnvironments() (or the snippet above) before getSmartAccountsEnvironment(5042) — kit …

    line 291
  19. Very specific:

    Use when the host or backend executes work using a user-granted delegation (session keys, offline execution, periodic ERC-20 grants).

    line 381
  20. Very specific:

    Use when a coding agent holds an EOA in Foundry keystores (aw.mjs) and must redeem through the public relayer without printing private keys.

    line 421
  21. Very specific:

    Write a child input JSON with to: targetAddress, scope narrower than the parent (erc20TransferAmount ≥ fee + work for this submit.

    line 433
  22. Very specific:

    Do not use aw send for gas-abstracted redeem — that broadcasts a raw EOA tx.

    line 440

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

1Shot Public Relayer (EIP-7710) Integration

This skill teaches a client-side coding agent how to integrate with the 1Shot public relayer JSON-RPC API to submit gas-abstracted ERC-7710 delegated transactions.

Install this skill (consumer / global)

npx skills add 1Shot-API/skills/public-relayer -g -a claude-code -y

The relayer accepts a signed MetaMask delegation from a 7702StatelessDelegator smart account, redeems it on-chain through a target wallet, and accepts payment in an ERC-20 token on the same chain (single-chain) or a different chain (multichain).

When to use this skill

  • Building a client app/SDK/wallet/agent that submits transactions through the 1Shot relayer
  • Upgrading an EOA to a MetaMask 7702StatelessDelegator smart account via EIP-7702
  • Creating, signing, and submitting ERC-7710 delegations
  • Estimating or quoting the relayer fee for a transaction before submit
  • Locking in a relayer gas price for a quote window
  • Receiving and verifying signed webhook events from the relayer
  • Redeeming a signed delegation context returned by the embedded 1Shot Wallet (wallet_requestExecutionPermissions) or MetaMask (requestExecutionPermissions)

When NOT to use this skill

  • Embedding the 1Shot Wallet for standard sends — eth_sendTransaction through OWSProxy; the wallet calls relayer_* internally. Use the 1shot-wallet skill only.
  • Theming, connect, credentials, analytics — configure, eth_requestAccounts, OID4, proxy.analytics; 1shot-wallet only.
  • Optional tx status webhooks from embed — set configure.destinationUrl; the wallet passes it to the relayer on send. Still no host relayer client.

Endpoints and packages

  • Relayer JSON-RPC URL: choose endpoint by chain before calling relayer_getCapabilities, relayer_getFeeData, relayer_estimate7710Transaction, relayer_estimate7710TransactionMultichain, relayer_send7710Transaction, relayer_send7710TransactionMultichain, or relayer_getStatus:
    • Mainnets: https://relayer.1shotapi.com/relayers
    • Sepolia (11155111) and Base Sepolia (84532): https://relayer.1shotapi.dev/relayers
  • JWKS for webhook verification: GET https://relayer.1shotapi.com/.well-known/jwks.json (Ed25519, kty: "OKP", crv: "Ed25519").
  • Client packages:
    • @metamask/smart-accounts-kit (recommend ^2.0.0; required for Arc Testnet and Robinhood Chain) — single package for all delegation and smart-account flows:
      • Main export: toMetaMaskSmartAccount, createDelegation, ScopeType, Implementation.Stateless7702, getSmartAccountsEnvironment, PREFERRED_VERSION.
      • @metamask/smart-accounts-kit/actions: erc7715ProviderActions for browser EIP-7715 flows.
      • @metamask/smart-accounts-kit/utils: decodeDelegations to decode permission context from the wallet; overrideDeployedEnvironment to register chains missing from the published deployment registry (see Arc mainnet below).
    • viem for createPublicClient, encodeFunctionData, signAuthorization, privateKeyToAccount.
    • @noble/ed25519 (or any Ed25519 verifier) for webhook signature verification.

Method naming: multichain variants append Multichain directly to the base method name (no underscore), e.g. relayer_send7710Transaction → relayer_send7710TransactionMultichain.

Migration note: @metamask/delegation-toolkit is deprecated. Remove it from client package.json and use @metamask/smart-accounts-kit with the subpath imports above.

Arc mainnet (5042): register Smart Accounts environment

@metamask/delegation-deployments (pulled in by the kit) lists Arc Testnet (5042002) but not Arc mainnet (5042), even though MetaMask Framework v1.3.0 is CREATE2-deployed at the same addresses on both. Without an override, getSmartAccountsEnvironment(5042), toMetaMaskSmartAccount, and EIP-7702 auth targeting the Stateless7702 impl throw:

No contracts found for version 1.3.0 chain 5042

Patch once at process/module startup (before any kit call for chain 5042). Copy the Arc Testnet environment onto Arc mainnet via overrideDeployedEnvironment. Idempotent: skip if getSmartAccountsEnvironment(5042) already succeeds (future kit releases may ship 5042 natively).

import {
  getSmartAccountsEnvironment,
  PREFERRED_VERSION,
} from "@metamask/smart-accounts-kit";
import { overrideDeployedEnvironment } from "@metamask/smart-accounts-kit/utils";

const ARC_MAINNET = 5042;
const ARC_TESTNET = 5042002;

export function registerSmartAccountsEnvironments(): void {
  try {
    getSmartAccountsEnvironment(ARC_MAINNET);
    return;
  } catch {
    // Missing from published registry — inject Arc Testnet's identical env.
  }
  const template = getSmartAccountsEnvironment(ARC_TESTNET);
  overrideDeployedEnvironment(ARC_MAINNET, PREFERRED_VERSION, template);
}

registerSmartAccountsEnvironments();

Call this from each entrypoint that uses the kit on Arc (host app bootstrap, worker, or CLI). The agent-wallet skill applies the same side-effect in scripts/lib/registerSmartAccountsEnvironments.mjs so aw auth7702 / delegation-* / redelegate work with --chain-id 5042. The embedded 1Shot Wallet Branding Layer registers the same override internally — hosts that only use Path A do not need this in their own code.

Browser extension integration (recommended for UI apps)

When the user is signing in a browser with MetaMask (or another extension wallet), prefer an extension-first flow:

  1. Create a wallet client from window.ethereum with createWalletClient({ transport: custom(window.ethereum) }).
  2. Extend it with erc7715ProviderActions() from @metamask/smart-accounts-kit/actions.
  3. (Optional) Call getSupportedExecutionPermissions() to verify the wallet supports the permission type you need.
  4. Request permissions through the extension via requestExecutionPermissions(...). Set to to the relayer's targetAddress from Step 1 — not a dapp session account. The relayer redeems delegations granted directly to its redemption wallet.
  5. Decode context with decodeDelegations(context) from @metamask/smart-accounts-kit/utils, run each delegation through toRelayerJson, and pass the result as permissionContext in relayer_send7710Transaction.

Wallet prerequisites (see execute on a MetaMask user's behalf):

  • ERC-20 periodic permissions require MetaMask Flask ≥13.5 or MetaMask production ≥13.23.
  • The user must be upgraded to a MetaMask Smart Account (EIP-7702). Check with publicClient.getCode(address) — if code is present, compare the delegator address (strip the 0xef0100 prefix) against getSmartAccountsEnvironment(chainId).implementations.EIP7702StatelessDeleGatorImpl. Prompt the user to upgrade if needed.

Why this path matters: extension wallets own the keys and permission UX. Local/internal signing helpers like signDelegation are the right fit for backend or script-driven signers, but they can fail or mislead in extension flows.

If requestExecutionPermissions is unavailable, surface this clearly: the connected wallet likely does not support EIP-7715 and the app should prompt the user to switch wallets.

Order of operations

Follow these steps in order for any new integration.

Step 1 — relayer_getCapabilities: discover the chain, accepted tokens, and targetAddress

Call once per chain you want to support. The response tells you:

  • Which chainIds are supported.
  • The list of accepted ERC-20 payment tokens (address, symbol, decimals) per chain.
  • The feeCollector address (where the fee transfer must go).
  • The targetAddress — this is the address the client must sign the delegation to. It is the relayer's redemption account on that chain. Without delegating to this exact address, the relayer cannot redeem.

Cache the result for the session; it changes rarely.

// Request
{ "jsonrpc": "2.0", "id": 1, "method": "relayer_getCapabilities", "params": ["8453", "84532"] }

// Response (excerpt)
{ "result": {
  "8453": {
    "feeCollector": "0x1111...",
    "targetAddress": "0x2222...",     // ← delegate `to` this address
    "tokens": [{ "address": "0x036C...", "symbol": "USDC", "decimals": "6" }]
  }
}}

Step 2 — Build and sign the bundle (client-side)

Before quoting or submitting, assemble the full ERC-7710 bundle:

  1. Initialize the smart account with toMetaMaskSmartAccount({ implementation: Implementation.Stateless7702, address: <delegator EOA address>, signer }).
  2. (If first use on that chain) sign an EIP-7702 authorizationList entry targeting getSmartAccountsEnvironment(chainId).implementations.EIP7702StatelessDeleGatorImpl. Use the account’s current pending nonce from publicClient.getTransactionCount({ address, blockTag: "pending" }). Do not pass viem executor: "self" — that signs nonce + 1 (EOA-as-outer-sender); the relayer sends the outer tx and requires the unbumped pending nonce. Include the same authorizationList on estimate and send. Agent-wallet: aw auth7702 --rpc … --chain-id N already signs this way.
  3. Generate permission context:
    • Browser extension path: call requestExecutionPermissions (with to: targetAddress), decode granted[0].context with decodeDelegations, and serialize with toRelayerJson.
    • Local signer path: create a delegation with createDelegation({ to: targetAddress (from Step 1), from: smartAccount.address, environment: smartAccount.environment, salt, scope: { type: ScopeType.Erc20TransferAmount | ScopeType.FunctionCall, ... maxAmount: feeAmount + workAmount } }) and sign with smartAccount.signDelegation({ delegation }).
  4. Encode each execution's calldata (encodeFunctionData for the fee transfer to feeCollector and for the user's primary work).

Include a mock fee payment execution: an ERC-20 transfer to feeCollector with amount ≥ minFee for that token (use relayer_getFeeData or a conservative placeholder such as 0.01 USDC). The relayer parses this leg to determine the payment token. For multichain, only the fee-chain param needs the fee leg; the work-chain param can omit it.

For user-entered token amounts, parse decimal strings with token decimals first (for example, parseUnits("0.01", 6) for USDC). Never pass decimal strings directly to BigInt, because "0.01" is invalid and leads to runtime failures.

Step 3 — relayer_estimate7710Transaction or relayer_estimate7710TransactionMultichain: quote the fee (preferred)

Once the signed bundle exists, call the matching estimate method with the same params shape as send (omit context and delegationSecret; taskId, destinationUrl, and memo are optional and ignored for pricing):

You need …Use this method
Pay fee and execute work on the same chainrelayer_estimate7710Transaction
Pay fee on chain A and execute work on chain B (or batch multiple chains atomically)relayer_estimate7710TransactionMultichain

The relayer validates delegations, runs 1Shot gas simulation, and returns synchronously (no task is created):

  • success (boolean) — false when validation or simulation fails; read error in the result (not always a JSON-RPC error).
  • requiredPaymentAmount (string, token atoms) — fee to pay in the mock payment token, floored at chain/token min fee. Safe for BigInt(...).
  • gasUsed — map of chain id → summed gas units (decimal strings).
  • context — signed price-lock quote for the first payment chain; pass as params.context on single-chain send.
  • contextByChainId — per-chain signed quotes; for multichain send, set each params[i].context = contextByChainId[params[i].chainId].

Each signed quote embeds the chain (and gasPrice/rate) it was issued for. On send, the relayer rejects a context whose signed chain does not match that param's chainId (4217 QUOTE_CHAIN_MISMATCH). Never reuse one chain's quote on another.

Production loop:

  1. POST estimate with the current bundle (mock fee ≥ floor — e.g. parseUnits("0.01", decimals) for USDC).
  2. If success === false, fix the bundle from error (missing payment, below min fee, simulation revert, invalid delegation).
  3. If requiredPaymentAmount differs from your mock fee, update the fee execution amount and delegation maxAmount, then re-sign the delegation.
  4. Immediately send with the context / contextByChainId from the estimate response to lock the quote (~30 seconds TTL). Match each param's context to that param's chainId. If the user approved a fee cap, and the quote aged out before send, re-estimate and send only if the new fee is still ≤ that cap.

This supersedes client-side eth_estimateGas + manual fee math when the full bundle is ready.

Units: minFee (decimal) vs requiredPaymentAmount (atoms)

FieldMethodUnitExample (USDC, 6 decimals)Safe for BigInt(x)?
minFeerelayer_getFeeDataHuman decimal string"0.01"No — use parseUnits(minFee, decimals)
requiredPaymentAmountrelayer_estimate7710Transaction*Integer string of token atoms"10000" (= 0.01 USDC)Yes
Fee / work in executions and delegation maxAmountyour bundleatoms (bigint)10000nn/a

Never do BigInt(feeData.minFee) when minFee is "0.01" — that throws. Prefer estimate’s requiredPaymentAmount for the final fee leg. For a mock floor before estimate, use parseUnits("0.01", token.decimals) (or parseUnits(feeData.minFee, feeData.token.decimals)).

Step 2b (fallback) — relayer_getFeeData: rough quote before the bundle exists

Use when the bundle is not built yet — for example, browser permission UX that needs a rough fee before the user signs, or a quick price display. Call once per (chainId, paymentToken). The response contains:

  • gasPrice (hex wei) — current relayer gas price in native gas units.
  • rate (number) — exchange rate to convert native gas cost into the payment token amount.
  • minFee (string, human-readable decimal, e.g. "0.01") — floor ≈ $0.01 in the payment token. Convert with parseUnits(minFee, token.decimals) before comparing to atom amounts.
  • expiry (unix seconds) — quote validity (~30 seconds by default; treat anything past expiry as stale).
  • context (string) — signed price-lock context (prefer the context returned from estimate when the bundle exists).

Manual fee math (when estimate is unavailable):

  1. Estimate gasUsed for the work transaction(s) (use eth_estimateGas or a known upper bound).
  2. nativeFee = gasPrice * gasUsed (wei).
  3. Convert to payment-token atoms using rate.
  4. Floor: feeAmount = max(converted, parseUnits(minFee, decimals)).

The delegation's caveat scope must allow at least feeAmount to be transferred to feeCollector. Under-paying causes InsufficientPayment (4200).

Step 4 — relayer_send7710Transaction or relayer_send7710TransactionMultichain: submit the bundle

Choose a signing path first:

  • Browser extension path (recommended for UI apps): request permissions with requestExecutionPermissions, decode with decodeDelegations, and send decoded delegations in permissionContext.
  • Local signer path (backend/scripts): build and sign delegations with createDelegation + smartAccount.signDelegation (see Step 2).

POST the JSON-RPC body built in Steps 2–3. The params.context field must be the signed price-lock context from the matching estimate response (result.context for single-chain; result.contextByChainId[chainId] per param for multichain), not a free-form string.

Choose the right method:

You need …Use this method
Pay fee and execute work on the same chainrelayer_send7710Transaction
Pay fee on chain A and execute work on chain B (or batch multiple chains atomically)relayer_send7710TransactionMultichain

Both methods accept an optional destinationUrl (≤256 chars). When set, the relayer POSTs signed Ed25519 webhook events to that URL on every status change. Encourage destinationUrl over polling — it scales better and gives sub-second updates.

Both methods also accept an optional memo (≤256 chars): an opaque client correlation string (order ID, internal ref, etc.). It does not affect execution, pricing, or on-chain calldata — the relayer stores it and echoes it back in relayer_getStatus and webhook payloads when set. Omit on estimate (same param type is accepted but ignored for pricing, like taskId / destinationUrl). For multichain, each array entry may carry its own memo (each becomes a separate task).

Recommended on send: delegationSecret (10–1024 characters when provided). Pick one secret per client or app (not per delegation): generate it once, store it in your app config or secure client storage, and pass the same value on every send from that source. You do not need a different secret for each delegation or a delegation→secret map — the relayer binds each delegation to whichever secret you used on its first submit with a secret present, and later resubmits of that delegation must use that same secret. A third party replaying your on-chain delegation without your app’s secret is rejected (4216). When omitted, unbound delegations still work (legacy behavior); already-bound delegations are rejected without the secret. Omit on estimate. For multichain, you may repeat the same app secret on each chain entry.

The result is a TaskId (single) or TaskId[] (multichain, in submitted order).

Step 5 — relayer_getStatus: check status (or use webhooks)

If destinationUrl is set, prefer the webhook — the relayer POSTs signed JSON on each status change. Outbound webhook bodies use a numeric type field (not a string event name):

typeMeaning
4Submitted (on-chain tx hash available)
0Confirmed success
1Reverted / execution failure

The data field is the same object shape as relayer_getStatus for that task — including optional memo at data.memo when you sent params.memo on submit (omitted otherwise, never null).

To verify each webhook:

  1. Fetch and cache GET https://relayer.1shotapi.com/.well-known/jwks.json (rotates infrequently).
  2. Look up the public key by kid from the webhook body's keyId.
  3. Reconstruct the signed payload by removing the signature field, then serialize with stable, sorted-key JSON (use safe-stable-stringify or equivalent — the relayer signs the canonical form).
  4. Verify with Ed25519 over UTF-8 bytes; treat the base64 signature as the 64-byte detached signature.

If polling is unavoidable, call relayer_getStatus with { id: <TaskId>, logs: true|false } every 2–3 seconds and stop on a terminal status. When you sent params.memo, the status object includes memo at every status code; when omitted at send, the field is absent (not null). Status codes:

CodeLabelTerminal?
100Pendingno
110Submittedno (has hash)
200Confirmedyes (has receipt)
400Rejectedyes (has message)
500Revertedyes (has data)

Decisions cheat sheet

  • Quote fee how?: when the signed bundle exists, prefer relayer_estimate7710Transaction (single-chain) or relayer_estimate7710TransactionMultichain (multichain) — the relayer simulates gas and returns requiredPaymentAmount (atoms) plus signed context. Use relayer_getFeeData only for rough quotes before the bundle is built; its minFee is a human decimal ("0.01"), not atoms.
  • Transient HTTP/network errors (ECONNRESET, fetch failed, HTTP 502/503, timeouts): these are usually client ↔ network issues, not a JSON-RPC business error from the relayer. Retry estimate/send with backoff (2–3 attempts). If send may have succeeded, do not re-send blindly — poll relayer_getStatus with a saved taskId (or wait for webhook) before building a new submit.
  • Working directory: run agent-wallet as node scripts/aw.mjs … from the agent-wallet skill root (the directory that contains scripts/), or pass an absolute path to aw.mjs. Do not cd scripts and then node scripts/aw.mjs (path double-up).
  • Unsigned estimate (quote before signing): on estimate only, set every delegation signature to "", bytes32(0) (0x + 64 zero hex digits), or the recommended 65-byte all-zero placeholder; the relayer injects estimate-only DelegatorEstimateShim code at each delegator via Geth stateOverride (including EOAs not yet EIP-7702 upgraded on chain). Real on-chain token balances and allowances still apply — not faked. For a first-time EIP-7702 upgrade, you may also send authorizationList with correct address, chainId, and nonce but placeholder r/s (32-byte zero hex) and yParity 0 — only when delegation signatures are placeholders; the relayer adds fixed auth gas to the quote (default 40100 gas) and does not forward invalid auth to simulation. Send requires real delegation and authorization signatures — placeholders are rejected on relayer_send7710Transaction*.
  • Self-sponsored vs. sponsored: if the same account pays the fee and executes the work, sign one delegation that scopes feeAmount + workAmount and bundle two executions (fee transfer + work). If a separate sponsor pays the fee, sign two delegations (one each from sponsor and delegator) and submit two transactions[] entries with their own permissionContext. The relayer merges them into a single redeemDelegations batch.
  • ScopeType choice: ScopeType.Erc20TransferAmount is simplest and works for fee + work transfers. Use ScopeType.FunctionCall (token + selector) when you need broader function coverage in one batch — the Erc20TransferAmount enforcer can revert with CaveatEnforcer:invalid-call-type for some batched call patterns.
  • EIP-7702 authorization: only one authorizationList entry is allowed per request. If both delegator and sponsor need an upgrade, do them in two separate calls (or upgrade one out-of-band first). Sign with pending nonce (no executor: "self"). Pass on estimate and send for the first redeem on a chain.
  • Delegation chain order (leaf → root): permissionContext / encodeDelegations must be [child, parent, …] — the first delegation’s delegate must be the relayer targetAddress. Root-first [parent, child] fails estimate with First delegation's delegate must be the relayer Target wallet.
  • Scope amounts: under kit v2, pass maxAmount (and similar) as bigint (or let agent-wallet coerce decimal JSON strings). A decimal string like "1050000" passed raw is treated as hex digits and silently over-authorizes.
  • Salt: always pass a fresh random 32-byte hex salt to createDelegation to avoid replay collisions.
  • Arc mainnet (5042): call registerSmartAccountsEnvironments() (or the snippet above) before getSmartAccountsEnvironment(5042) — kit registry has Testnet only; addresses match via CREATE2. Arc RPC for auth7702: https://rpc.mainnet.arc.io.
  • memo vs context vs taskId vs delegationSecret: context locks the fee quote from estimate; taskId is the relayer-assigned (or client-supplied) task identifier; memo is your opaque label echoed back in status and webhooks for correlation; delegationSecret is one app-level secret reused on all your sends (not one secret per delegation) to bind open delegations against public replay (send only).
  • BigInts to JSON: relayer JSON-RPC requires plain JSON. Convert bigint values in the signed delegation struct to 0x-prefixed hex strings before sending. Convert Uint8Array with bytesToHex from viem/utils.

Minimal end-to-end shape

// 1. capabilities
const caps = await rpc("relayer_getCapabilities", [chainId]);
const { targetAddress, feeCollector, tokens } = caps[chainId];
const paymentToken = tokens.find((t) => t.symbol === "USDC")!;

// 2. build + sign bundle (mock fee ≥ minFee; see examples.md)
const sendParams = {
  chainId,
  transactions: [
    {
      permissionContext: [signedDelegation],
      executions: [feeTransfer, workCall],
    },
  ],
};

// 3. estimate (same params as send, no context)
const estimate = await rpc<Estimate7710Result>(
  "relayer_estimate7710Transaction",
  sendParams,
);
if (!estimate.success) throw new Error(estimate.error);
const feeAmount = BigInt(estimate.requiredPaymentAmount!);
// if feeAmount changed: rebuild fee execution + delegation scope, re-sign, re-estimate

// 4. submit with price lock from estimate
const taskId = await rpc("relayer_send7710Transaction", {
  ...sendParams,
  context: estimate.context,
  destinationUrl: "https://my-app.example.com/relayer-webhook", // optional, recommended
  memo: "order-abc123", // optional; echoed in relayer_getStatus and webhooks
  delegationSecret: APP_DELEGATION_SECRET, // one per app/client; reuse on every send, not per delegation
});

// Multichain: estimate with params array, then send each entry with
// context: estimate.contextByChainId![param.chainId]

// 5. either consume webhooks or poll relayer_getStatus

Common error codes

Estimate responses: relayer_estimate7710Transaction and relayer_estimate7710TransactionMultichain return result.success: false with an error string for validation and simulation failures (missing mock payment, fee below minFee, gas estimation revert). These are not always JSON-RPC errors — check result.success before send.

CodeMeaningTypical fix
4200Insufficient PaymentIncrease feeAmount to at least requiredPaymentAmount / minFee; re-sign.
4201Invalid SignatureRe-sign the delegation; ensure salt is fresh and signer matches from.
4202Unsupported Payment TokenPick a token from relayer_getCapabilities for the chain.
4204Quote ExpiredRe-run estimate (or re-fetch relayer_getFeeData) and resubmit within ~30s.
4206Unsupported ChainConfirm the chainId appears in relayer_getCapabilities.
4209Unsupported CapabilityAdjust delegation scope/caveats; check the relayer supports the call type.
4210Invalid Authorization ListAt most one authorizationList entry; verify nonce is current.
4211Simulation FailedThe relayer pre-simulates; inspect data for the revert reason.
4212Multichain Not SupportedFall back to relayer_send7710Transaction per chain.
4214Duplicate Task IDOmit taskId and let the relayer assign one, or send a fresh random hex.
4216Delegation Secret MismatchUse your app’s delegationSecret (the one bound on first submit for that delegation), or omit only if never bound.

Browser-flow pitfalls and fixes

  • "wallet_requestExecutionPermissions does not exist": wallet does not support EIP-7715. Prompt the user to switch to a compatible wallet.
  • "External signature requests cannot sign delegations for internal accounts": wrong signing path. Use extension permission requests, not internal-account delegation signing.
  • "Account does not support signMessage": signer/account mismatch. In extension mode, avoid local-account assumptions.
  • "Cannot convert 0.01 to a BigInt": parse decimal inputs with token decimals first, then convert.

See references/schemas.md for the complete error catalog and full request/response schemas.

Integration paths with 1shot-wallet

Three distinct paths — pick one before writing code:

PathSkillsHost calls relayer_*?
A — Embed only1shot-walletNo
B — Embed + execute delegations1shot-wallet + public-relayerYes (redeem grant)
C — No embedded walletpublic-relayerYes

Path A — Embed only (most common)

Install 1shot-wallet only. Wire OWSProxy, call eth_sendTransaction — the wallet signs delegations and submits to the relayer internally. No host relayer JSON-RPC code. Gas-abstracted sends and optional configure.destinationUrl webhooks work without this skill.

Path B — Embed + execute delegations (both skills)

Use when the host or backend executes work using a user-granted delegation (session keys, offline execution, periodic ERC-20 grants). Install both skills; read 1shot-wallet/SKILL.md for OWSProxy setup. Start with relayer_getCapabilities and note targetAddress (relayer redemption wallet on that chain).

Sub-pathGrant toHost redelegates?Example
B1 — DirecttargetAddressNoExample 0b
B2 — Session key (recommended)Host session accountYes → targetAddressExample 0c

Path B1 — Direct grant to relayer

Simpler prototype path: user grants the relayer directly.

  1. wallet_requestExecutionPermissions with to: targetAddress and periodic ERC-20 scope (budget ≥ fee + work).
  2. Wallet returns response.context (single signed delegation); host decodes with decodeDelegations + toRelayerJson, builds executions[], then relayer_estimate7710Transaction / relayer_send7710Transaction.

User delegates to the relayer address directly — fine for demos; prefer B2 for production session keys.

Path B2 — Session key + redelegation (recommended)

Production session-key path: user grants a host-controlled session account; host redelegates to the relayer with a narrower, execution-specific scope before submit.

  1. Host generates or loads a session account (local signer; private key stays in host memory — not the user's passkey).
  2. wallet_requestExecutionPermissions with to: sessionAccountAddress and periodic ERC-20 scope (offline/agent budget).
  3. Wallet returns response.context (parent grant). Host decodes; session account redelegates to targetAddress with narrower caveats for this execution (FunctionCall + exactCalldata, or Erc20TransferAmount capped to fee + work — same attenuation idea as in-wallet send).
  4. Build permissionContext as an encoded delegation chain in leaf-to-root order: encodeDelegations([signedRedelegation, rootDelegation]) (child first — child’s delegate is targetAddress). Estimate + relayer_send7710Transaction with that chain.
  5. Session account may need EIP-7702 smart-account shape on first use; include authorizationList (pending nonce, Stateless7702 impl) on estimate and send when upgrading.

Redelegation may only narrow parent scope (caveats stack). See MetaMask kit create redelegation (redelegatePermissionContext or createDelegation({ parentDelegation })).

The wallet also uses the relayer internally for in-wallet Send and for wallet_revokeExecutionPermission cancel — that is separate from Path B.

Reach for Path B when a developer asks to execute on the user's behalf while offline, redeem a periodic ERC-20 grant via relayer, or submit relayer bundles using a passkey-signed delegation from the embedded wallet — not for generic "embed a passkey wallet." Prefer B2 for session keys and agent execution.

The public relayer is an open JSON-RPC service (no API key). Anyone can run an alternate relayer speaking the same relayer_* methods.

Path C — No embedded wallet

MetaMask extension (requestExecutionPermissions + window.ethereum) or local/backend signers (createDelegation + signDelegation). The rest of this skill applies as written. See Example 0 in references/examples.md.

Path D — Agent wallet (local EOA via agent-wallet)

Use when a coding agent holds an EOA in Foundry keystores (aw.mjs) and must redeem through the public relayer without printing private keys. Install agent-wallet + this skill. Signing stays in aw; JSON-RPC stays here.

Sub-pathParent grantAgent signsExample
D1 — Self-sponsoredNone — agent creates a root delegation to: targetAddressaw delegation-sign (+ auth7702 on first use)Example 0d (D1)
D2 — User grant + redelegate (CoinOperated / session-key style)Owner granted periodic ERC-20 to the agent addressaw redelegate --parent … to targetAddress, then estimate/send with chain [child, parent] (leaf first)Example 0d (D2)

Order for D2 (most common after CoinOperated linking):

  1. Load the signed parent (e.g. CoinOperated GET /api/agent/delegations → write delegation string raw to parent.json). Never ask the user for the agent private key.
  2. relayer_getCapabilities → targetAddress, feeCollector, payment token for the work chain (and fee chain if multichain).
  3. First use on a chain (required): node scripts/aw.mjs auth7702 --rpc https://rpc.mainnet.arc.io --chain-id 5042 (Arc) and attach authorizationList: [authorization] on estimate and send. Skip only after the EOA already has Stateless7702 code on that chain.
  4. Write a child input JSON with to: targetAddress, scope narrower than the parent (erc20TransferAmount ≥ fee + work for this submit; amounts as decimal strings — aw coerces to bigint), fresh salt.
  5. node scripts/aw.mjs redelegate --parent parent.json --file child.json → signedDelegation (child).
  6. Build permissionContext as [toRelayerJson(child), toRelayerJson(parent)] (or decodeDelegations(encodeDelegations([child, parent]))). First entry’s delegate must equal targetAddress.
  7. Bundle executions[] (fee transfer to feeCollector + work). For CoinOperated agents, prefer work = transfer USDC to the agent address, then trade from the agent wallet (see coinoperated skill). Prefer relayer_estimate7710Transaction then send with context (30s lock). Reuse one app-level delegationSecret.
  8. Batching: multiple executions in one transactions[] entry share one permissionContext (same chain). Multiple transactions[] entries = multiple permission contexts in one redeem (e.g. sponsor + worker).
  9. Multichain: build one param per chain (each with its own redelegation/authorizationList as needed); use relayer_estimate7710TransactionMultichain / relayer_send7710TransactionMultichain and set each params[i].context = contextByChainId[chainId].

Do not use aw send for gas-abstracted redeem — that broadcasts a raw EOA tx. Relayer redeem is always relayer_* JSON-RPC after aw signing.

Foundry cast vs agent-wallet + this skill

GoalTool
Create keystore, SIWE personal_sign, EIP-7702 auth, sign/redelegate EIP-7710agent-wallet aw.mjs (Node + ethers/viem; cast optional)
Gas-abstracted redeem (fee in USDC, no native gas on the agent)This skill — relayer_estimate* / relayer_send*
Raw EOA broadcast that spends native gas (cast send / aw send)Foundry cast only — not the grant/relayer path

End-to-end agent pattern: aw auth7702 + aw redelegate → assemble permissionContext + executions → estimate (retry on ECONNRESET) → send with context. Worked example: Example 0d. CoinOperated wiring + direct vs pull paths: skill coinoperated + references/spend-paths.md.

Composing with webauthn-prf-wallet for a fully non-custodial app

The public relayer pairs naturally with the webauthn-prf-wallet skill (separately installed) to build a fully non-custodial web3 application with no vendor lock-in and no business account required:

  • webauthn-prf-wallet derives an EVM private key from the user's passkey via the WebAuthn PRF extension and keeps it inside an isolated iframe — the key never reaches the parent page or any server.
  • That passkey-derived account is the natural delegator in this skill's flow: have the iframe sign the EIP-7702 authorization, the 7702StatelessDelegator upgrade, and each createDelegation payload.
  • The public relayer is an open JSON-RPC service (no API key, no Bearer token) that the user pays per-transaction in stablecoins. There is no relationship to lock in — anyone can stand up an alternate relayer that speaks the same relayer_* methods, and the client can switch by changing RELAYER_URL.

End-to-end, the user owns their key (passkey), pays only the per-tx fee (ERC-20), and the application owns no custodial surface and no business credentials. Reach for this combo when a developer asks for a "passkey wallet that can transact without holding ETH" or "non-custodial app with no API keys to manage." Read webauthn-prf-wallet/SKILL.md for the client-side wallet pattern and use this skill for the relayer JSON-RPC flow.

Additional resources

  • references/schemas.md — full JSON-RPC method signatures, schemas, status/error codes, JWKS body shape. Read this when you need exact parameter shapes, the complete error catalog, or the on-the-wire webhook payload format.
  • references/examples.md — runnable TypeScript patterns: MetaMask browser flow (Example 0), embedded wallet direct grant (Example 0b, Path B1), embedded wallet session key + redelegation (Example 0c, Path B2), agent-wallet CLI + redelegate (Example 0d, Path D), estimate-first single-chain and multichain flows, self-sponsored, sponsored, webhook receiver with Ed25519 verification. Read this when you're about to write client code or want to copy a known-good integration shape.
  • 1shot-wallet skill — embed the 1Shot Wallet (OWS Host Layer). Required for Path A and Path B; companion for Path B delegation execution only (not required for embed-only sends).
  • agent-wallet skill — local agent EOA (aw.mjs: auth7702, delegation-sign, redelegate). Required for Path D; pair with this skill for relayer JSON-RPC.
  • coinoperated skill — owner deeplink grant + agent SIWE; then Path D2 here to spend.
  • webauthn-prf-wallet skill — companion skill for client-side passkey-derived EVM keys held in an isolated iframe. Use together with this skill for a fully non-custodial setup.
  • MetaMask Smart Accounts Kit — install, browser / EIP-7715 flow, local signer / delegation flow, Advanced Permissions wallet-client reference.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.