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 residue

The 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:

  1. The transaction exists and did not fail (meta.err is null).
  2. The order's reference key is among the transaction's accounts.
  3. For every payee, postBalance − preBalance ≥ amount owed. Balances are checked per account, not by parsing instructions, so it holds however the transaction was assembled.
  4. 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.

NextEncryption →