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.
Activate that receipt and it starts earning from the pool. Every later sale at that business adds to the pool, and it divides across activated receipts in proportion to how many each person holds. Claim whenever you like.
An unactivated receipt earns nothing, and its share goes to holders who did activate. That is the whole mechanism.
Glossary#
| Term | What it means |
|---|---|
| Receipt | An ERC-721 NFT minted on purchase. Records the business, the product, the price and the date. Yours to hold, transfer or sell. |
| Activation | A one-off, permanent step that switches a receipt into an earning state. Until then it earns nothing. |
| Pool | The share of each sale set aside for holders. Between 1% and 90%, fixed at deployment. |
| Pool share | Your activated receipts divided by every activated receipt 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. 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: it shows in any wallet, on any explorer, and can be sold on any marketplace.
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.
Activation#
A receipt earns only once activated. Activation is a one-off with a small fee, and it is permanent.
Why it costs anything. Without a price, every receipt ever minted would be activated, the pool would divide across all of them, and nobody would earn enough to care. A price filters to holders who expect to earn more than they pay.
It is not retroactive. A receipt joins the pool at the moment it activates and earns from sales after that, never from sales before. Activating early is worth more than activating late.
activate(uint256[] tokenIds)
activated(uint256 tokenId) โ bool
activeCount() โ uint256
The pool#
Rewards use an accumulator rather than a payment per holder per sale. A business keeps a running total of reward per activated receipt, and each receipt records where it joined. What you are owed is the difference.
pending = accPerReceipt - checkpoint[tokenId]
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 whoever holds the receipt. Sell it with unclaimed rewards inside and they go to the buyer. Claim first if you do not want that.
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 payout 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.
Royalties#
Receipts are freely transferable and can be sold anywhere. On a resale, the business takes the royalty it set at deployment, up to 10%, and Spend2Stake takes 1.5%.
ERC-2981 allows one recipient, so the royalty is paid to the business contract and split
afterwards by sweepRoyalties(), which anyone can call. It only ever pays
the business's own payout address and the platform, and it cannot touch rewards owed to
holders.
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 payout address |
| Claim | the receipt holder | that holder |
Neither is a decision anyone makes. The business sets its own payout 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
royaltyBps() โ the resale royalty
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 |
| activate | the receipt owner | switches receipts into earning |
| claim | the receipt owner | pays out accrued rewards |
| sweepRoyalties | anyone | splits resale royalties to fixed addresses |
| 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)
Activated(tokenId, holder, fee)
Claimed(holder, amount, tokenIds)
RoyaltiesSwept(toBusiness, toPlatform)
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.