Build your gacha on FLETCHER
Your storefront, your brand — our machines, custody, settlement, and card data underneath. The same API fletcher.cards itself runs on, open to any builder: no API keys, no contracts to sign, and a built-in 1% referral revenue stream on every pack you route.
#Overview
Every card on FLETCHER is a physical, professionally graded slab (PSA / CGC / BGS) held in insured custody and represented on-chain as a MirrorCard ERC-721 backed 1:1 by the physical card. Users rip gacha packs, trade on the marketplace, sell back instantly, battle, and redeem the physical slab to their door. All settlement is on-chain in USDG on Robinhood Chain.
- Rips — 29 live machines across Pokémon, One Piece, Sports, and Marvel, from $25 to $2,500 a pack.
- Marketplace — member resales settled entirely on Robinhood Chain, plus buys and escrow-backed offers on the 68,000-card custodied catalog.
- Exits — instant buyback in the first 72 hours (85–93% by machine), marketplace listings, or physical redemption shipped worldwide.
The core guarantee: every paid rip terminates in exactly one of two states — a MirrorCard in the buyer's wallet, or a full refund. There is no third state. Stuck flows resolve automatically.
#Start with AI
The fastest way in: paste this prompt into your coding agent (Claude, Cursor, or any LLM assistant). It carries every constant, flow, and rule your agent needs to scaffold a working integration — written for machines, not humans.
You are building an integration on FLETCHER (fletcher.cards), a real-asset graded-card gacha platform. Physical PSA/CGC/BGS slabs sit in insured custody; each is mirrored 1:1 as a MirrorCard ERC-721 on Robinhood Chain. Users rip gacha packs, trade, sell back, and redeem physical cards. You will build against FLETCHER's public worker API and on-chain contracts. No API key exists; do not invent one.
CONSTANTS
- Chain: Robinhood Chain, chainId 4663 (EVM). Explorer: https://robinhoodchain.blockscout.com
- Settlement token: USDG, ERC-20 at 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168, 6 decimals. All amounts on the wire are 6-decimal base units as decimal STRINGS.
- Worker API base: https://fletcher-worker.onrender.com
- Contract discovery: GET /config returns { intake, mirror, mover, treasury, usdg, chainId, vaultRecipient }. Resolve addresses at runtime; never hardcode anything except USDG.
FIRST ACTIONS (do these before writing integration code)
1. GET /machines — the live gacha catalog: slug, name, category, priceUsd, evUsd, odds, stock, buybackPct, noTurbo, noBuyback.
2. GET /config — contract addresses and vaultRecipient.
3. GET /agents.md — the full, always-current API reference as raw markdown. Read it; it supersedes anything stale here.
CORE INVARIANT
Every paid rip terminates in exactly one of two states: a MirrorCard minted to the buyer, or a full refund of the deposit. There is no third state. A stuck flow self-heals server-side.
THE MONEY RAIL (every paid flow follows these five steps)
1. Health gate: GET /health must return 2xx before any wallet interaction.
2. Generate a sagaId: 32 cryptographically random bytes, 0x-hex (bytes32). It is BOTH the on-chain deposit tag and the worker's job key. Fresh per attempt; on-chain reuse reverts.
3. Arm the worker FIRST: POST the intent (e.g. POST /rip) BEFORE moving money so the deposit watcher is armed.
4. Approve + deposit: check USDG allowance(user, intake); approve if short (infinite approval is normal); after the approve receipt RE-POLL allowance before depositing (stale RPC nodes cause spurious reverts). Then call Intake.deposit(sagaId, amount) — ABI: deposit(bytes32,uint256); payer(bytes32) view returns (address).
5. Poll to terminal every 2-5 s. Rip terminal states: completed (card minted; mirror.tokenId + txHash), auto_sold (turbo Common paid out as USDG), refunded. A poll timeout is NOT failure — the saga completes server-side; re-hydrate from GET /rips/<wallet>.
DEPOSIT MATH (6-decimal base units)
- Rip / CC-marketplace buy / offer / gift: ccBase = round(priceUsd * 1e6); buffer = 250000 + ccBase/1000; deposit = ccBase + buffer (+ referral fee if attached). The buffer covers the bridge hop to Solana where the physical card settles.
- Member-marketplace buy / redemption: deposit the EXACT quoted amount. No buffer. Adding one overcharges the user.
- Referral (optional partner revenue): register a handle via POST /referral {handle, wallet}; then on each rip add referrer + referralFeeUsdc = ccBase/100 (1%) to the body and add that 1% to the deposit. It pays the referrer wallet automatically after the rip.
KEY ENDPOINTS
- Rip: POST /rip { id: sagaId, user:{base}, machineSlug, pricePaidUsdc, gachaCostUsdc, vaultRecipient, turbo } then poll GET /rip/<id>. turbo:true auto-sells a Common pull for instant USDG.
- CC-marketplace buy: same POST /rip with machineSlug:"" and a listing:{nftAddress, priceUsd, currency, tokenStandard, sellerWallet, card} object.
- Member marketplace: GET /member-listings; buy via POST /member-buy { id, buyer:{base}, seller:{base}, cardMint, mirrorTokenId, priceUsdc } + exact deposit, poll GET /member-buy/<id>. List: POST /list (signed). Delist: POST /delist (signed).
- Offers on CC cards: POST /offer { id, user:{base}, cardMint, ccCardId, priceUsd, offerUsdc, sellerWallet, card } + deposit(bid+buffer); placement is terminal at state "open" (1-2 min); the FILL can take days — track GET /offers-mine/<addr>; cancel via signed POST /offer-cancel.
- Instant sell (within 72h of the RIP): GET /sell-quote/<mint>, then signed POST /sell at EXACTLY the quoted amount, poll GET /sell/<id>. Requires one-time ERC-721 setApprovalForAll(user -> mover address from /config).
- Gifts: POST /gift/create (rip-style deposit; returns a bearer claim token — treat the link as money), POST /gift/claim, POST /gift/open, GET /gift/<token>, GET /gifts/<wallet>.
- Battles: GET /battles, GET /battle/<id>; POST /battle/create (signed), POST /battle/join (signed) then deposit EXACTLY the returned depositUsdc; seat holds 10 min; retrying a rejected payment reuses the SAME sagaId (the one sanctioned reuse).
- Physical redemption: POST /redeem-quote {insuredUsd, country}; POST /redeem (signed, shipping details); deposit the WORKER's returned quote exactly; poll POST /redeem-status (signed).
- Inventory & card data: GET /rips/<addr> (durable inventory), GET /card-by-token/<tokenId>, GET /value/<tokenId>, GET /card/<mint>, GET /meta/<tokenId>, GET /holders, GET /recent-openings.
- Social (optional): GET /feed, /posts/<wallet>, /post/<id>, /profile/<addr-or-@handle>, /notifications; writes POST /post, /post/like, /post/delete, /follow, /unfollow. Realtime: wss://fletcher-worker.onrender.com/ws (read-only; types chat, bchat, pull).
AUTH MODELS
- Reads: public, no auth.
- POST /rip, /member-buy, /offer, /gift/create: NO signature — the tagged on-chain deposit IS the authorization.
- Signed endpoints use EIP-191 personal_sign over byte-exact templates, ts = ms epoch within +/-10 min. Templates (copy exactly; prefixes are historical): "SCALPERS: list <cardMint> for <priceUsd> USDC ts <ts>", "SCALPERS: delist <cardMint> ts <ts>", "SCALPERS: sell <cardMint> saga <id> ts <ts>", "SCALPERS: cancel-offer <cardMint> saga <id> ts <ts>", "FLETCHER: redeem <cardMint> saga <sagaId> ts <ts>", "FLETCHER: battle-create <machineSlug> x<seatsTotal> ts <ts>", "FLETCHER: battle-join <battleId> saga <sagaId> ts <ts>", "FLETCHER: post <keccak256-hex-of-text> ts <ts>", "FLETCHER: social like|follow|unfollow|delete <target> ts <ts>", "FLETCHER: notifications <wallet> ts <ts>".
HARD RULES (violating these loses user money or double-charges)
1. Buffered vs exact deposits: rips/CC-buys/offers/gifts add the buffer; member-buys/redemptions are exact. Never mix these up.
2. Order matters: health gate -> POST intent -> approve -> deposit -> poll.
3. Fresh sagaId per money attempt (except battle payment retry).
4. Initiate payment only from an explicit user gesture, never from a render effect (React StrictMode double-fires effects and will double-charge).
5. Never retry a user-rejected wallet action. Retry stale-node simulation reverts up to 3x.
6. Fetch the live price at pay time and abort if it exceeds the displayed price.
7. Quotes (sell, redeem) are live and must be used exactly; never cache or recompute them.
8. Amounts are strings in JSON. Convert with round(dollars * 1e6).
9. The 72h buyback window runs from RIP time.
10. Poll at 2-5 s, not tighter; catalog endpoints are server-cached.
Support: https://t.me/fletchercards (include the sagaId in any money question). CORS: server-side calls need nothing; for browser calls from a new domain, ask FLETCHER to allowlist the origin.
Start by fetching /machines, /config, and /agents.md, then confirm your integration plan with the developer before writing money-path code.#Quick facts
| Chain | Robinhood Chain, chainId 4663 |
| Settlement token | USDG 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 (6 decimals) |
| Worker API base | https://fletcher-worker.onrender.com |
| Contract discovery | GET /config → intake, mirror, mover, treasury, usdg, vaultRecipient |
| API keys | None. Reads are public; money moves are authorized on-chain or by wallet signature |
| Live API reference | GET /agents.md — always current, raw markdown |
Discover contract addresses from /config at runtime. The only address safe to pin is USDG. FLETCHER adds no markup on packs — priceUsd is the live pack price.
#The gacha catalog
29 machines, live right now. GET /machines is the authoritative list (prices, odds, and stock move; machines get added and retired) — poll it rather than pinning this snapshot.
Pokémon
| Machine | Slug | Price | Buyback | Turbo |
|---|---|---|---|---|
| Starter Pokémon Pack | pokemon_25 | $25 | 85% | yes |
| Elite Pokémon Pack | pokemon_50 | $50 | 85% | yes |
| Sealed Pokémon Pack | sealed_80 | $80 | 90% | yes |
| Fire & Grass Pack | firegrass_100 | $100 | 90% | yes |
| Water Pack | water_100 | $100 | 90% | yes |
| 151 & Friends Pack | pokemon_151 | $151 | 90% | yes |
| Legendary Pokémon Pack | pokemon_250 | $250 | 90% | yes |
| Charizard Pack | echarizard_250 | $250 | 90% | yes |
| Vintage 90s Pack | ev90s_500 | $500 | — | no |
| Grail Pokémon Pack | pokemon_1000 | $1000 | 93% | yes |
| Mythic Pokémon Pack | pokemon_2500 | $2500 | 93% | yes |
One Piece
| Machine | Slug | Price | Buyback | Turbo |
|---|---|---|---|---|
| One Piece Ocean Blue Pack | onepiece_50 | $50 | 85% | yes |
| One Piece Gacha Pack | onepiece_250 | $250 | 90% | yes |
| One Piece Emperor Pack | onepiece_1000 | $1000 | 93% | yes |
Sports
| Machine | Slug | Price | Buyback | Turbo |
|---|---|---|---|---|
| Football Pack | football_50 | $50 | 85% | yes |
| Soccer Pack | soccer_50 | $50 | 90% | yes |
| Baseball Pack | baseball_50 | $50 | 85% | yes |
| Basketball Pack | basketball_50 | $50 | 85% | yes |
| Sports Pack | sports_100 | $100 | 90% | yes |
| Football Elite Pack | football_100 | $100 | 90% | yes |
| Soccer Elite Pack | soccer_100 | $100 | 90% | yes |
| Baseball Elite Pack | baseball_100 | $100 | 90% | yes |
| Basketball Elite Pack | basketball_100 | $100 | 90% | yes |
| Football Legendary Pack | football_250 | $250 | 90% | yes |
| Soccer Legendary Pack | soccer_250 | $250 | 90% | yes |
| Baseball Legendary Pack | baseball_250 | $250 | 90% | yes |
| Basketball Legendary Pack | basketball_250 | $250 | 90% | yes |
Marvel
| Machine | Slug | Price | Buyback | Turbo |
|---|---|---|---|---|
| Spider-Man Pack | espider_25 | $25 | 85% | yes |
| Spider-Man Elite Pack | espider_50 | $50 | 85% | yes |
The Vintage 90s Pack is the special case: every pull is a real 1990s card ($250–$50k range), no turbo, no instant buyback — exits are the marketplace or physical redemption.
#The money rail
Every paid flow — rips, marketplace buys, offers, gifts, battles, redemption — follows the same five steps. Only the deposit math and endpoints differ.
// every paid flow, same rail 1. GET /health // gate: 2xx or stop 2. sagaId = 0x + 32 random bytes // deposit tag AND job key 3. POST the intent (e.g. POST /rip) // arm the watcher BEFORE money moves 4. USDG.approve(intake) if short // then RE-POLL allowance Intake.deposit(sagaId, amount) // once per sagaId — reuse reverts 5. poll every 2-5s to terminal // completed | auto_sold | refunded
The sagaId is the idempotency guarantee: it tags the on-chain deposit AND keys the worker job, and the Intake contract rejects a second deposit under the same id. All amounts cross the wire as 6-decimal base-unit strings.
#Rips
// deposit math (6-decimal base units, sent as strings)
ccBase = round(priceUsd * 1e6) // live price from GET /machines
buffer = 250000 + ccBase / 1000 // $0.25 + 0.10% bridge buffer
deposit = ccBase + buffer // + your 1% referral fee if attached
POST /rip
{
"id": "<sagaId>",
"user": { "base": "<0xBuyer>" },
"machineSlug": "pokemon_50",
"pricePaidUsdc": "<deposit>",
"gachaCostUsdc": "<ccBase + buffer>",
"vaultRecipient": "<from GET /config>",
"turbo": false
}- Turbo —
"turbo": trueauto-sells a Common pull instantly and pays the buyer USDG cash instead of the card. - Price safety — fetch the live price at rip time and abort if it exceeds what your user saw (we abort at +$0.01). Never charge more than displayed.
- Terminal states —
completed(mirror minted),auto_sold(turbo cash-out),refunded(deposit returned).
#Marketplace
Member resales — Robinhood-only, exact amounts, no buffer
- Browse
GET /member-listings; one seller viaGET /listings/<addr>. - Buy:
POST /member-buywith the listing's exactpriceUsdc, then deposit exactly that — nothing bridges, a buffer here would overcharge. PollGET /member-buy/<id>. - List / delist: gasless signed messages to
POST /listandPOST /delist.
Custodied-catalog buys and offers — bridge buffer applies
- Buy a specific listed card: same
POST /ripwithmachineSlug: ""and alistingobject; deposit = ask + buffer. - Offers:
POST /offer, deposit = bid + buffer. Placement is terminal at stateopen(1–2 min); the fill can take days — trackGET /offers-mine, cancel via signedPOST /offer-cancel(escrow refunds).
#Instant sell
- Window: 72 hours from the rip (not from listing). Rates are per-machine, 85–93%.
GET /sell-quote/<mint>→ signedPOST /sellat exactly the quote → pollGET /sell/<id>(~3 min). Gasless for the user.- Requires a one-time ERC-721
setApprovalForAllto themoveraddress from/config(a verified contract, never an EOA).
#Gifts, battles, redemption
- Gifts — pay now, a friend opens later.
POST /gift/create(rip-style deposit) returns a bearer claim token; treat the link as money. Claim, open, and poll like a normal rip. - Battles — multiplayer pack battles, winner takes all cards.
GET /battles, signed create/join; the join response'sdepositUsdcis authoritative. Seats hold 10 minutes. - Physical redemption —
POST /redeem-quote, signedPOST /redeemwith shipping details, deposit the worker's quote exactly. Fees are the custodian's bill passed through, no markup.
#Inventory & card data
GET /rips/<addr> | A wallet's terminal rips + sells — the durable inventory source |
GET /card-by-token/<id> | Normalized card for a mirror token: mint, grade, set, year, image, insured value, live owner |
GET /value/<id> | Live USD value: insured value + live buyback quote |
GET /card/<mint> | Full card metadata by mint |
GET /holders | Every mirror-holding wallet, biggest bags first, with profile info |
GET /recent-openings | Every real pull since launch — live-ticker material |
wss://…/ws | Read-only realtime: pull (new openings), chat, bchat |
The on-chain mirror name is capped at 32 bytes and may truncate — read grade fields from the API, never parse the NFT name. Card/metadata endpoints are CORS *.
#Your revenue
Partner monetization is built into the rip path. Register a referral handle bound to your payout wallet, attach it to every rip you route, and the buyer's +1% is wired to your wallet automatically after each rip completes — on-chain, per rip, no invoicing.
POST /referral { "handle": "yourproject", "wallet": "0xYourWallet" }
// then on every rip you route:
"referrer": "0xYourWallet",
"referralFeeUsdc": "<ccBase / 100>" // 1% of the pack price, paid by the buyer
// and add that 1% to the depositThe 1% is the only fee in the FLETCHER rip path and is paid by the buyer on top of the pack price. Be transparent about it in your UI.
#Auth models
| Model | Used by | How |
|---|---|---|
| Public | All reads | Nothing |
| Deposit-is-auth | /rip, /member-buy, /offer, /gift/create | The tagged on-chain deposit IS the authorization — no signature |
| Wallet signature | /sell, /list, /delist, /offer-cancel, /redeem, battles | EIP-191 over byte-exact templates, ts within ±10 min (full list in the AI prompt above) |
| Wallet claim | Unsigned social, chat, profile edits | Spoofable by design — display as unverified |
#Gotchas that will save you a week
- Buffered vs exact deposits. Rips, catalog buys, offers, gifts add the bridge buffer; member-buys and redemptions deposit the exact amount. Getting this backwards strands funds or overcharges.
- Order of operations is load-bearing. Health gate → POST the saga → approve → deposit → poll.
- Fresh sagaId per money attempt. Reuse reverts on-chain (the idempotency working). Only sanctioned reuse: battle payment retry.
- Money moves only on user gestures. Never initiate payment from a render effect — React StrictMode double-invokes effects and will double-charge in dev.
- Re-poll allowance after approving before depositing — stale RPC nodes cause spurious reverts.
- Never retry a wallet rejection. Retry stale-node simulation reverts up to 3×; a user rejection rethrows immediately.
- Poll timeouts are not failures. The saga finishes server-side — re-hydrate from
/rips/<wallet>. - Quotes are live. Sell and redeem at exactly the quoted amount; never cache a quote.
#Going live
From us
- This page, plus the always-current reference at
GET /agents.md. - CORS allowlisting for your web origin(s) — server-side calls need nothing.
- A referral handle so you earn 1% on every rip you route.
- A direct line: t.me/fletchercards — include the sagaId in any money question.
From you
- Your web origin(s) for CORS.
- Your referral payout wallet.
- A heads-up before launch so we can watch the first transactions land.
No keys, no contracts to sign, no revenue share owed to us — the buyer pays the pack price, the bridge buffer, and (if you attach one) your 1%.