0fra

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.

LayerWhoWallet variableCut
L00fra (us)service_walletservice_fee_bps (set by 0fra)
L1Your platformplatform_walletplatform_fee_bps (set by you)
L2Sub-merchantmerchant_walletWhatever'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

TermStripe analog0fra equivalent
Accountacct_…platform.id (uuid)
Connected acctAccountmerchant.id (uuid)
Payment intentpi_…order.id (uuid)
Chargech_…payment_attempt (per tx_hash)
Checkout sess.cs_…cs_live_… token
API keysk_live_…Same. Plus pk_live_… publishable.
Webhook eventevt_…Webhook delivery (per attempt)

How payments actually move

sequenceDiagram
    actor Buyer
    participant Wallet as Buyer wallet
    participant Router as PaymentRouter
    participant 0fra as 0fra treasury
    participant Plat as Platform wallet
    participant Merch as Merchant wallet
    participant Vault as ReserveVault
    Buyer->>Wallet: approve USDC
    Buyer->>Router: payOrder(params, signature)
    Router->>Router: verify EIP-712 + registries + amount
    Router->>0fra: transfer service fee
    Router->>Plat: transfer platform fee
    Router->>Merch: transfer merchant net (minus reserve)
    Router->>Vault: hold reserve
    Router-->>Buyer: emit OrderPaid

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
          └─► expired

What 0fra keeps off-chain

  • A ledger of every dollar that passed through (ledger_entries table)
  • 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.

On this page