Skip to content

Protocol

Security model

What the rail0 contract guarantees, what it trusts, and the trade-offs it accepts on purpose. The short version: no one can change the rules, no one can be granted a standing claim on anyone’s funds, and the only ways money moves are the calls the contract defines, each gated by a party and a time window. The rest of this page is the detail, including the cases where that design costs something and why it is kept anyway.

  • No privileged roles. No owner, pauser or upgrade path. The code and the token allowlist are fixed at deployment.
  • No allowances. Every movement from a wallet into the contract is an EIP-3009 signature for one specific operation; nothing standing is ever granted.
  • Signatures can’t be front-run. Every signature names the rail0 contract as the recipient and is spent through receiveWithAuthorization, which the token accepts only when the caller is that recipient. The merchant submits the transaction, but the call to the token comes from rail0, so a signature copied from a pending transaction is useless to anyone who sends it to the token directly.
  • Reentrancy-safe. Every entrypoint that calls a token is guarded, and all state changes happen before any transfer (checks–effects–interactions).
  • Tolerant transfers. Outbound transfers accept tokens that return bool and tokens that return nothing, and revert TransferFailed on any failure.

The deployer chooses which tokens a deployment accepts, and the contract trusts them to behave like standard ERC-20s with EIP-3009. Allowlisting a hostile or non-standard token is the deployer’s risk.

There is no arbitration layer. Funds go back to the buyer only through void (while nothing is captured), release (after authorizationExpiry) or the merchant’s discretionary refund. Disputes are a censorship-resistant signal with no effect on funds — so they carry no payoff to abuse, and acting on them happens off-chain.

These follow from keeping the contract small and immutable. Each is pinned by a test so it cannot change silently.

  • A frozen merchant can’t strand the buyer. If the token issuer freezes the merchant, capture fails, but void and release still return the escrow to the buyer.
  • A frozen buyer can strand their own escrow. If the issuer freezes the buyer after authorization, every buyer-bound payout fails. Before authorizationExpiry the merchant can still capture; after it, the escrow has no way out. Capture within the window.
  • A partial capture holds the rest until expiry. After any capture, void is closed and release waits for authorizationExpiry, so the remainder is held until then even if both parties want it back — capturing a single unit is enough. Nothing is lost, only delayed: choose authorizationExpiry to match your fulfilment time, not the maximum.
  • A dispute opened at the last moment only the buyer can close. A merchant clears a dispute with a full refund, which ends at refundExpiry; the buyer can withdraw at any time. React to PaymentDisputed well before the deadline.
  • Keep refund windows bounded — 14 to 30 days, in line with consumer-protection rules — since a signed but unsubmitted refund stays valid until refundExpiry.
  • Generate paymentIds with a collision-resistant scheme (a UUID, or a hash of the parties and an order nonce).
  • Pin the deployment address and version per chain.