Gasless retirement (paid relay)
Retire carbon without holding native ETH, without a prior token approval, and without a Base Account — sign a single token authorization and a Klima executor submits the transaction for you.
The relay path lets any wallet or agent retire carbon by signing one standard EIP-712 token authorization. A Klima executor submits the on-chain transaction and is reimbursed for gas out of your signed budget. For the path where you submit the transaction yourself, see Retire carbon with x402.
What you sign
You sign the token authorization, not the retirement. The only signature is a standard token authorization:
This is exactly what any x402-style payment signs. Client signing is therefore plain eth_signTypedData_v4; there is no custom typed data to assemble.
On the USDC path the authorization's nonce is not random: it is keccak256(retirement, salt) — the exact credit, token id, amount, and full attribution struct being authorized, plus a fresh 32-byte salt the server mints and returns inside actionsRetireRequest. Because nonce is a signed EIP-3009 field, your signature covers what gets retired and who is credited, not just the spend value. actions/retire rebuilds the retirement, re-hashes it with the submitted salt, and returns 400 params_mismatch on a mismatch.
Post actionsRetireRequest back verbatim, salt included (USDC). Without salt the authorization cannot be verified (400 invalid_auth_payload). The kVCM (Permit) path carries no commitment and no salt — Permit's nonce is the token's own counter.
Before you begin
You will need:
A wallet holding an accepted input token (USDC or kVCM) — no ETH required
A client capable of
eth_signTypedData_v4
Attribution (required)
A relayed retirement must name its beneficiary. Pass one of:
details.beneficiaryAddress— the party the retirement is for, orbeneficiaryIsPayer: true— credit the paying wallet deliberately
Omitting both returns 400 attribution_required. Attribution is permanently indexed on-chain and cannot be changed after confirmation.
The flow
Prepare the authorization
POST /api with prepare-auth. The server resolves and prices the retirement and returns typedData (the EIP-712 object to sign) and actionsRetireRequest (a ready-to-send request body, including salt on the USDC path). Check onChainDetails before signing — it is exactly what the authorization commits to.
prepare-auth is the 200 alias of the 402 challenge that actions/retire returns when it is posted without an authPayload. Either entry point gives you the same typedData.
How the signed budget works
The signed budget (authValue) covers the retirement, the protocol fee, and the executor's gas reimbursement, with a slippage buffer. The signer needs only an input-token balance (USDC or kVCM) — no ETH. Send the actionsRetireRequest body verbatim so that from, to, and (on USDC) salt match the signed authorization.
actions/retire responses
settled
Mined and indexed — retirements[] carries the certificate URL(s).
pending_index
Mined or broadcast, but the subgraph has not caught up — poll /certificate with the tx hash.
400 attribution_required
No beneficiary named — set details.beneficiaryAddress or beneficiaryIsPayer: true.
400 insufficient_authorized_value
Your signed value no longer covers retirement + fee + gas (price or gas moved). Re-run prepare-auth and re-sign.
400 invalid_auth_payload
from ≠ request from, to ≠ settlement contract, wrong payload shape, or USDC path missing salt. Use the actionsRetireRequest body verbatim.
400 params_mismatch
Submitted retirement does not match the authorized USDC nonce commitment. Re-post verbatim or re-prepare and re-sign.
422 transaction_reverted
Mined but reverted on-chain; no retirement recorded.
See x402 reference for the full error registry.
Reference clients
SDK — zero-dependency TypeScript client
sdk/klima-retire.ts. Oneretire()call runs prepare-auth → sign → submit → poll certificate. PassbeneficiaryIsPayer: trueordetails.beneficiaryAddress.Examples — runnable scripts under
examples/(retire/retire-sdk.ts,retire/paid-retire.ts,retire/retire-raw.ts).
Notes
You sign a token authorization, not the retirement itself — and on USDC that authorization binds the retirement via
nonce+salt.The signer needs only an input-token balance — no ETH.
Name a beneficiary (
details.beneficiaryAddressorbeneficiaryIsPayer: true) before signing.Re-run
prepare-authand re-sign if you hitinsufficient_authorized_value; do not blind-retry.Send the
actionsRetireRequestbody verbatim (includingsalton USDC) to keep the signature valid.Retirement is irreversible once the transaction confirms.
Last updated