Core concepts
The mental model behind 0fra — platforms, merchants, fees, and ledger.
Three layers, three wallets
Every payment touches exactly three parties on-chain. Each is a distinct wallet address.
| Layer | Who | Wallet variable | Cut |
|---|---|---|---|
| L0 | 0fra (us) | service_wallet | service_fee_bps (set by 0fra) |
| L1 | Your platform | platform_wallet | platform_fee_bps (set by you) |
| L2 | Sub-merchant | merchant_wallet | Whatever's left, minus their reserve |
bps(basis points) is 1/10000.100 bps = 1%,1000 bps = 10%.
The split formula
const serviceFee = gross * serviceFeeBps / 10_000;
const platformFee = gross * platformFeeBps / 10_000;
const merchantNet = gross - serviceFee - platformFee;
const reserveHold = merchantNet * reserveBps / 10_000;
const merchantAvailable = merchantNet - reserveHold;The on-chain PaymentRouter.payOrder() runs the same arithmetic and reverts if the values you submit don't match the registries. You can't tamper with the math.
Reserves
A reserve is a percentage of every payment held back from the merchant for a configurable period — used to cover refunds without bouncing tx through wallets.
- Set per-merchant on creation (
reserve_bps), with a platform-wide default - Reserves accrue in a 0fra-controlled vault, indexed by
(merchant_id, token, chain_id) - Refund flow draws from the reserve first; if insufficient, the platform absorbs and rolls a negative balance for the merchant
Identity
| Term | Stripe analog | 0fra equivalent |
|---|---|---|
| Account | acct_… | platform.id (uuid) |
| Connected acct | Account | merchant.id (uuid) |
| Payment intent | pi_… | order.id (uuid) |
| Charge | ch_… | payment_attempt (per tx_hash) |
| Checkout sess. | cs_… | cs_live_… token |
| API key | sk_live_… | Same. Plus pk_live_… publishable. |
| Webhook event | evt_… | Webhook delivery (per attempt) |
How payments actually move
The dotted-line on the right represents a single atomic transaction. Either everything happens or nothing does.
Account state machine
order:
pending_payment ──► confirmed ──► refunded / partially_refunded
└─► expired
merchant:
pending_review ──► active ──► frozen
invitation:
pending ──► completed
└─► expiredWhat 0fra keeps off-chain
- A ledger of every dollar that passed through (
ledger_entriestable) - The idempotency cache so retries are safe
- Webhook delivery queue with HMAC signatures and exponential retries
- API keys (only their SHA-256 hash) and session cookies for the dashboard
What's never off-chain: the actual transfer. That always lives on Base.