Docs · 08
Payments
Native SOL, two transfers in one transaction, the reference key, and how the server verifies.
No contract of ours#
A purchase is a plain Solana transaction built in the buyer's browser from two SystemProgram.transfer instructions. Nothing of EVERYNTH's runs on-chain, so there is no program to audit, upgrade, pause or drain. What can go wrong is bounded to the one order in front of you.
The split#
Prices are stored in lamports (1 SOL = 1,000,000,000 lamports). For a price P:
fee = floor(P × 500 / 10 000) // 5%, rounded down
creator share = P − fee
fee + creator share == P // always, no residueThe buyer pays exactly P plus the Solana network fee. If the creator and the treasury are the same wallet the two legs are merged into one transfer.
Minimum price is 0.02 SOL: Solana rejects a transfer that would leave an account under its rent-exempt minimum (about 0.0009 SOL), and 5% of 0.02 clears that even for a brand-new wallet. Maximum is 1,000,000 SOL, which keeps every amount inside JavaScript's safe integer range.
The order#
Pressing Buy calls POST /api/orders. The server freezes the payout terms — creator address, creator amount, treasury address, fee — and generates a fresh keypair whose public key is the order's reference. The private half is thrown away; the reference only needs to be unique and unguessable.
{
"purchaseId": "d80f8bf3-…",
"reference": "9kQd…3FaR",
"creator": "GfdY…9NsY", "creatorAmount": 19000000,
"treasury": "n4Xb…H7Pt", "fee": 1000000
}Freezing matters: if the creator changes the price after you pressed Buy, your order still settles at the old terms.
The transaction#
instruction 1: SystemProgram.transfer(buyer → creator, creatorAmount)
+ reference key appended as a read-only, non-signer account
instruction 2: SystemProgram.transfer(buyer → treasury, fee)Attaching the reference as an extra account is the Solana Pay convention. It costs nothing, changes nothing, and makes the transaction findable by getSignaturesForAddress(reference).
Verification#
After the wallet reports the transaction confirmed, the browser calls POST /api/orders/:id/confirm with the signature. The server never trusts that call; it fetches the transaction from its own RPC and checks:
- The transaction exists and did not fail (
meta.erris null). - The order's reference key is among the transaction's accounts.
- For every payee,
postBalance − preBalance ≥ amount owed. Balances are checked per account, not by parsing instructions, so it holds however the transaction was assembled. - The signature has not settled another purchase. The database enforces this with a unique constraint, so a single transaction that carries two references still pays for one.
Only then is the purchase marked paid and the key released.
Recovery#
If the confirm call never arrives (closed tab, crashed browser), the next POST /api/orders for the same product finds the open order and searches the chain for a transaction carrying its reference. A paid order is settled on the spot and the API answers 409 you already own this; the page then shows Unlock. See Buying.
RPC and confirmation#
The browser confirms at confirmed commitment using the RPC in NEXT_PUBLIC_RPC_URL; the server verifies with RPC_URL (falls back to the public one). If the server's RPC lags, confirm returns 402 with payment not found yet and the browser retries five times, two seconds apart.