Docs
How Spend2Stake works, what it charges, and how to check every claim on this page against the contracts rather than taking our word for it. This page describes the mechanism, which does not change. Anything that does change is read live from the contracts.
The short version#
Spend2Stake is an open marketplace running on Robinhood Chain. A business deploys its own contract and lists what it sells. Nobody approves it.
When you buy something, the payment splits in that same transaction: the business is paid, Spend2Stake takes commission, and a share the business chose at deployment stays in the contract as a reward pool. You receive an NFT receipt.
The receipt earns from the moment you buy. Every later sale at that business adds to the pool, and the pool divides across receipts in proportion to their weight: what you paid, times an early-buyer multiplier. Claim whenever you like.
Receipts cannot be sold or sent. To leave, you exit: the contract pays what the receipt has earned and burns it for good.
Glossary#
| Term | What it means |
|---|---|
| Receipt | An ERC-721 NFT minted on purchase. Records the business, the price and the date. It stays in your wallet: it cannot be sold or sent. |
| Early multiplier | Set when you buy. Starts at 10× and falls as more USDG is staked in that business, with no floor. It rises again when holders exit. |
| Exit | Pays a receipt's earnings and burns it permanently. The only way out. |
| Pool | The share of each sale set aside for holders. Between 1% and 90%, fixed at deployment. |
| Pool share | Your receipts' weight divided by the total weight at that business. |
| SpenScore | A public trust signal out of 100, built from verification, delivery record, official channel confirmation and user reports. |
| Boost | Paid promotion. Boost points lift a listing for 48 hours, then expire. |
| USDG | The stablecoin everything settles in. Gas is paid in ETH. |
Receipts#
Every purchase mints one ERC-721 token to the buyer. It is a standard NFT, locked to the buyer (ERC-5192): it shows in any wallet and on any explorer, and cannot be transferred or sold.
The buyer is a parameter rather than the caller, so a payment made on someone's behalf still mints the receipt to them. That is what allows card payments to be added later without changing the contract.
Collection
Collection is recorded, not settled, because the business was already paid. The buyer's device produces a signature which the business scans, so an order cannot be marked collected without the buyer present.
markRedeemed(bytes32 code, bytes signature)
redeemDigest(bytes32 code) → bytes32 // what the buyer signs
A signature works for exactly one order at one business and cannot be replayed.
Multiplier and exit#
Weight. Each receipt has a weight, fixed at purchase: the amount paid times the early multiplier. The multiplier is the square root of 100,000 divided by the USDG currently staked, capped at 10×. There is no floor, so a crowded business can pay less than 1× per USDG, and a new one pays up to 10×.
Exit. A holder cannot sell a receipt. The only way to leave is
exit, which pays everything the receipt has earned and burns it. The staked
amount falls, so the next buyer gets a higher multiplier. A burned receipt cannot be
revived; it stays on the explorer as a record and in your profile under Burned receipts.
exit(uint256[] tokenIds)
totalStaked() → uint256
weightOf(uint256 tokenId) → uint256
exitedAt(uint256 tokenId) → uint64
locked(uint256 tokenId) → bool // always true
The pool#
Rewards use an accumulator rather than a payment per holder per sale. A business keeps a running total of reward per unit of weight, and each receipt records where it joined. What you are owed is the difference.
pending = weightOf[tokenId] × (accPerWeight - checkpoint[tokenId]) / 1e18
One sale is one storage write regardless of whether ten people or ten thousand are holding, which is what makes the model work at any size.
Claiming#
Claiming is pull-based. Nothing is pushed to you, so nothing depends on us running a job. Call it whenever the amount is worth the network fee.
pendingRewards(address holder) → uint256
receiptsOf(address holder) → uint256[]
claim(uint256[] tokenIds)
Rewards belong to the wallet that holds the receipt. Exit pays any unclaimed rewards in the same transaction.
claim straight from a block
explorer and be paid. See Verify it yourself.
Commission#
Marginal bands, like income tax. Each rate applies only to the portion of an order inside it, so a larger order never pays a smaller fee than a smaller one.
| Portion of an order | Rate |
|---|---|
| First 30 USDG | 10% |
| 30 to 100 | 8% |
| 100 to 200 | 7% |
| 200 to 500 | 6% |
| 500 to 2,000 | 5% |
| 2,000 to 10,000 | 4% |
| Above 10,000 | 2% |
Minimum 0.50 USDG per sale. No cap.
| Order | Commission | Effective |
|---|---|---|
| 10 | 1.00 | 10.00% |
| 100 | 8.60 | 8.60% |
| 500 | 33.60 | 6.72% |
| 2,000 | 108.60 | 5.43% |
| 10,000 | 428.60 | 4.28% |
| 100,000 | 2,228.60 | 2.22% |
How rates can change
- Cuts are immediate and reach every listed business at once. No re-listing.
- Rises are capped and delayed. Never above the contract ceiling, announced on-chain, and effective only after 30 days.
- Every business keeps the ceiling it joined on, recorded on its own contract and unchangeable afterwards.
The split#
A 100 USDG purchase at a business with a 10% reward pool:
| Goes to | Amount | When |
|---|---|---|
| The business | 81.40 | same transaction, to their own wallet address |
| The reward pool | 10.00 | stays in the contract for holders |
| Spend2Stake | 8.60 | same transaction |
Nothing is held back and nothing can be reversed. The only balance a business contract ever carries is rewards owed to holders.
Boost#
Paid promotion. Points lift a listing, and bigger packs cost less per point.
| Points | Price | Per point | Duration |
|---|---|---|---|
| 200 | $19 | $0.095 | 48 hours |
| 500 | $44 | $0.088 | 48 hours |
| 1,000 | $79 | $0.079 | 48 hours |
| 5,000 | $299 | $0.060 | 48 hours |
Points expire on their own. The score is calculated from purchases still inside their window, so a boost fades rather than stopping dead, and nothing has to sweep it.
Anyone can boost any business, including a customer. Boosted listings are labelled, boosting does not affect SpenScore, and it implies no endorsement.
No resale#
Receipts are soulbound. They cannot be sold, sent or listed on a marketplace, so there is no secondary market, no resale royalty and no fee on one. The only transfer that can happen is an agent reassignment of a stolen receipt, which waits 72 hours and is public.
This keeps the pool honest: a receipt's weight belongs to the person who paid for it, and leaving always costs the holder their place in the pool.
Custody#
Spend2Stake never holds user funds. The business contracts contain no withdraw, no sweep, no rescue, and no emergency function. Money leaves by exactly two routes:
| Route | Who triggers it | Where it goes |
|---|---|---|
| Settlement | automatic, at purchase | the business's own wallet address |
| Claim | the receipt holder | that holder |
Neither is a decision anyone makes. The business sets its own wallet address and we never choose where money goes.
Theft recovery#
A receipt reported stolen can be frozen immediately so it cannot be sold on, and after a 72-hour public delay it can be reassigned to its original holder.
freeze(uint256 tokenId, string reason)
queueReassign(uint256 tokenId, address to) // emits, then waits 72h
executeReassign(uint256 tokenId)
This power moves tokens only. There is no path from it to moving USDG to the agent. Every use emits a public event, and the delay means a misuse can be seen and objected to before it completes.
The agent key is intended to be a multisig, not a single wallet.
Suspension#
A listing can be hidden and brought back with one call. Nothing is ever deleted.
Suspension stops new sales and removes the listing from the site. It does not affect holders. Receipts continue to earn and can still be claimed, because a dispute between us and a business is not the fault of people who bought from it.
Every suspension and restoration is written to a public audit log with a reason. So is a decision to review reports and leave a listing up.
Verify it yourself#
Nothing here needs to be taken on trust. Every claim above can be checked against the contracts, using a block explorer and no wallet connection.
1. Read a business without connecting anything
Open its contract on the explorer and use the Read tab. These cost nothing and need no wallet:
poolBps() → the reward share, in basis points
totalStaked() → USDG currently staked, which sets today's multiplier
feeCeilingBps() → the most this business can ever be charged
platformFee(amount) → commission on an order of that size
poolBalance() → USDG currently owed to holders
activeCount() → how many receipts are earning
2. Check what you are owed
receiptsOf(yourAddress) → your token ids here
pendingRewards(yourAddress) → what you can claim, in USDG wei
3. Claim without this website
On the Write tab, connect the wallet holding the receipts and call:
claim([1841, 1907, 2033])
You pay only the network fee. The USDG arrives in your wallet.
4. Confirm there is no way out for us
Open the contract source and search it. There is no withdraw,
sweep, rescue, drain or emergency
function. If one existed, this whole page would be worth nothing.
Live contracts#
The shared contracts every business is built on. A business's own contract is linked from its page on this site, or findable by searching its name on the explorer.
Robinhood Chain, chain id 4663. Explorer at robinhoodchain.blockscout.com.
Functions#
| Function | Who can call it | What it does |
|---|---|---|
| purchase | anyone | pays, mints a receipt, splits in one transaction |
| markRedeemed | anyone with the buyer's signature | records a handover |
| exit | the receipt owner | pays earnings and burns the receipt |
| claim | the receipt owner | pays out accrued rewards |
| setPayoutAddress | the business owner | changes where it is paid |
| freeze / unfreeze | the agent | locks a stolen receipt |
| queueReassign | the agent | starts a 72 hour recovery |
| setSuspended | the platform | hides a listing, stops new sales |
| lowerBands | the platform | cuts commission, immediately |
| proposeRaise | the platform | proposes a rise, 30 day delay |
Events#
Everything that matters emits an event, so the whole history can be rebuilt from the chain without reading our database.
Purchased(code, buyer, tokenId, amount, claimBy)
Settled(code, toBusiness, toPool, toPlatform)
Redeemed(code, buyer)
Locked(tokenId)
Exited(holder, tokenId, paid, weight, payout)
Claimed(holder, amount, tokenIds)
Frozen(tokenId, reason)
ReassignQueued(tokenId, to, readyAt)
Reassigned(tokenId, from, to)
SuspendedSet(suspended, reason)
Boosted(business, payer, packId, points, price, expiresAt)
BandsLowered(rates, at)
RaiseProposed(rates, readyAt)
An indexer reading these can reconstruct every balance, every holder and every fee ever charged.