The three multi-party sequences in the API: who calls what, who signs what, and which webhooks fire. Every step links to the endpoint page where you can run it live.
The end-user pays from their own wallet; rip.fun never touches their funds.
Trying this on the sandbox? Fund your test wallet first. See Get testnet funds (Base Sepolia).
wallet_address + tier_id → the USDC-approve and
purchase transactions (calls[]). Creates nothing.calls[0] (USDC approve, which you can skip if the
allowance already covers it) then calls[1] (purchase) from their own wallet.request_id from the receipt logs, if you have it) so
rip.fun can link the reveal.FULFILLED, or use the purchase.fulfilled webhook. The revealed
cards land in the user's wallet.Two facts every smart-wallet integrator rediscovers the hard way — learned in rip.fun's own app, so you don't have to:
wallet_prepareCalls gas estimation (the
simulation sees no allowance yet). Every calls[] entry carries a
machine-readable kind — split on kind === "erc20-approve" / "set-approval-for-all" and submit those first, alone. Skip the approve
entirely when the current allowance already covers max_price.@alchemy/wallet-apis@5.2.x needs bigint coercion on
prepared-call fields (value/gas fields arrive as numbers or hex strings) or the first
EIP-7702 delegation throws Expected bigint, got: 0. Coerce before calling wallet_prepareCalls.Ship a revealed card to the end-user. The holder sends the redemption transaction themselves
(same prepare → send → submit pattern as a purchase), and the card isn't burned until the item
ships. Identify the card by purchase_id or token_id. Use token_id to
redeem a card you distributed yourself (no rip.fun purchase). The person who
sends the transaction, and whom you must KYC, is then the card's current holder.
KYC is your responsibility. rip.fun runs no identity (KYC) or AML checks
during redemption. Run whatever identity checks your jurisdiction and risk policy require
on the end user before you call prepare, and keep those records. prepare returns the transaction right away for any eligible card the holder owns.
The end user sends the redemption transaction. The API does not. prepare returns the transaction to send, and submit only records the transaction hash after it's sent. Neither endpoint sends anything. The
wallet holding the card must send the transaction itself, and signing a message is not the same as sending a transaction (a signature
popup does nothing on-chain). rip.fun can't do this one for the holder: only the card's
holder can send it.
No card leaves the wallet at this step. The holder's transaction only flags the card for redemption. rip.fun burns the card for real when the item ships. Expect the wallet to show a plain contract interaction with no token transfer. That is correct, not a failure.
idempotency_key). It validates the address, locks in the
cheapest rate, and returns the transactions to send in data.unsigned ({ chain_id, calls[] }, status PREPARED): the redemption flag
(kind: "burn") plus the shipping payment — a USDC approve
(kind: "erc20-approve") and payShipping (kind: "shipping-payment") locked to the quoted amount. The end user pays
shipping on-chain; you are never billed for it.unsigned.calls as on-chain transactions, in order (confirm and pay gas, a few cents on Base, plus the
quoted shipping in USDC) from the wallet holding the card. This flags the card for redemption
and pays shipping; it does not transfer the card. Capture the returned tx
hash. Keep the order — the payment call only succeeds after the burn call — and under account
abstraction the approve must be its own userop.{ redemption_id, tx_hash } (the hash from step 3) → BURN_SUBMITTED.redemption.updated webhooks: IN_FULFILLMENT → COMPLETED. An item never ships before its shipping payment is on-chain.Step 3 in the end user's wallet. Send the transactions, don't just sign a message:
// data.unsigned from POST /mystery/redemption/prepare — calls[] is:
// [0] kind "burn" flag the card for redemption
// [1] kind "erc20-approve" approve USDC to the shipping processor
// [2] kind "shipping-payment" pay the quoted shipping (runs AFTER the burn)
const { chain_id, calls } = unsigned;
// 1. make sure the wallet is on the right chain
await wallet.request('wallet_switchEthereumChain', [
{ chainId: '0x' + chain_id.toString(16) }
]);
// 2. eth_sendTransaction, in order: REAL transactions the holder signs + pays
// gas for. (personal_sign / signTypedData would not touch the chain.)
// Under account abstraction, batch [0] and [2] but keep [1] its own userop.
let burnTxHash;
for (const call of calls) {
const txHash = await wallet.request('eth_sendTransaction', [
{ from: holderAddress, to: call.to, data: call.data, value: '0x0' }
]);
if (call.kind === 'burn') burnTxHash = txHash;
}
// 3. hand the burn tx hash to your backend -> POST /mystery/redemption/submit
// { redemption_id, tx_hash: burnTxHash } Give the end-user instant cash for a pull. This is the combo pool's own loop, not a marketplace trade: the pack price already deposited a buyback float on-chain, the holder sells the card straight back to the pool out of that float, and the card is restocked into the pool it came from. Nothing is fronted and nothing is owed — the sale simply reduces the net revenue your share is calculated on.
Two rules the contract enforces, both of which quote pre-flights for you: only the card's original recipient may sell it back (a card that changed hands never can), and only before its buyback window expires. A batch is capped at 50 cards and is all-or-nothing on-chain.
eligible before going further.calls[] the holder signs. The first is a one-time setApprovalForAll, included only when it is actually missing — the pool moves the
card itself during the sale, so without it the whole transaction reverts. The second is the
sale. min_total_usdc is on-chain slippage protection: the pool re-reads the price
oracle at execution time, so the default allows 1% of downward drift.sellback.confirmed webhook. The amount is decoded from the on-chain event,
never from your request, because it is the deduction in your net revenue. rip.fun indexes the
same event independently, so whichever lands first wins and re-submitting is safe.gross − sell backs − fees = net), and your share of that net is paid out at the
end of the period — see payouts.