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.
Guarantees
Section titled “Guarantees”- 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
booland tokens that return nothing, and revertTransferFailedon any failure.
Trust boundary
Section titled “Trust boundary”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.
Recourse is time-based
Section titled “Recourse is time-based”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.
Accepted trade-offs
Section titled “Accepted trade-offs”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,
capturefails, butvoidandreleasestill 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
authorizationExpirythe 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,
voidis closed andreleasewaits forauthorizationExpiry, 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: chooseauthorizationExpiryto 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 toPaymentDisputedwell before the deadline.
Good practice
Section titled “Good practice”- 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.