FLETCHER is still under development — you're part of the beta. Rips might pause for a minute or two; any deposit already in flight is safe and completes automatically. Having an issue? Head to the Support tab.
Partners

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.

fletcher-integration-prompt.txt
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

ChainRobinhood Chain, chainId 4663
Settlement tokenUSDG 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 (6 decimals)
Worker API basehttps://fletcher-worker.onrender.com
Contract discoveryGET /config → intake, mirror, mover, treasury, usdg, vaultRecipient
API keysNone. Reads are public; money moves are authorized on-chain or by wallet signature
Live API referenceGET /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

MachineSlugPriceBuybackTurbo
Starter Pokémon Packpokemon_25$2585%yes
Elite Pokémon Packpokemon_50$5085%yes
Sealed Pokémon Packsealed_80$8090%yes
Fire & Grass Packfiregrass_100$10090%yes
Water Packwater_100$10090%yes
151 & Friends Packpokemon_151$15190%yes
Legendary Pokémon Packpokemon_250$25090%yes
Charizard Packecharizard_250$25090%yes
Vintage 90s Packev90s_500$500no
Grail Pokémon Packpokemon_1000$100093%yes
Mythic Pokémon Packpokemon_2500$250093%yes

One Piece

MachineSlugPriceBuybackTurbo
One Piece Ocean Blue Packonepiece_50$5085%yes
One Piece Gacha Packonepiece_250$25090%yes
One Piece Emperor Packonepiece_1000$100093%yes

Sports

MachineSlugPriceBuybackTurbo
Football Packfootball_50$5085%yes
Soccer Packsoccer_50$5090%yes
Baseball Packbaseball_50$5085%yes
Basketball Packbasketball_50$5085%yes
Sports Packsports_100$10090%yes
Football Elite Packfootball_100$10090%yes
Soccer Elite Packsoccer_100$10090%yes
Baseball Elite Packbaseball_100$10090%yes
Basketball Elite Packbasketball_100$10090%yes
Football Legendary Packfootball_250$25090%yes
Soccer Legendary Packsoccer_250$25090%yes
Baseball Legendary Packbaseball_250$25090%yes
Basketball Legendary Packbasketball_250$25090%yes

Marvel

MachineSlugPriceBuybackTurbo
Spider-Man Packespider_25$2585%yes
Spider-Man Elite Packespider_50$5085%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.

the shared rail
// 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

rip a pack
// 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": true auto-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 statescompleted (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 via GET /listings/<addr>.
  • Buy: POST /member-buy with the listing's exact priceUsdc, then deposit exactly that — nothing bridges, a buffer here would overcharge. Poll GET /member-buy/<id>.
  • List / delist: gasless signed messages to POST /list and POST /delist.

Custodied-catalog buys and offers — bridge buffer applies

  • Buy a specific listed card: same POST /rip with machineSlug: "" and a listing object; deposit = ask + buffer.
  • Offers: POST /offer, deposit = bid + buffer. Placement is terminal at state open (1–2 min); the fill can take days — track GET /offers-mine, cancel via signed POST /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> → signed POST /sell at exactly the quote → poll GET /sell/<id> (~3 min). Gasless for the user.
  • Requires a one-time ERC-721 setApprovalForAll to the mover address 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's depositUsdc is authoritative. Seats hold 10 minutes.
  • Physical redemptionPOST /redeem-quote, signed POST /redeem with 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 /holdersEvery mirror-holding wallet, biggest bags first, with profile info
GET /recent-openingsEvery real pull since launch — live-ticker material
wss://…/wsRead-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.

referral setup
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 deposit

The 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

ModelUsed byHow
PublicAll readsNothing
Deposit-is-auth/rip, /member-buy, /offer, /gift/createThe tagged on-chain deposit IS the authorization — no signature
Wallet signature/sell, /list, /delist, /offer-cancel, /redeem, battlesEIP-191 over byte-exact templates, ts within ±10 min (full list in the AI prompt above)
Wallet claimUnsigned social, chat, profile editsSpoofable 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%.