PAYRAIL

Whitepaper

A controlled, auditable batch settlement rail for stablecoin payouts on Arc — v0.1, testnet.

Abstract

payrail is a payout rail for finance teams that pay many counterparties in stablecoins: contractors, vendors, affiliates, marketplace sellers and refund queues. It takes a payout file, commits it on-chain as a hash, requires a second signer to approve that exact payload, and then settles every recipient in one transaction against an allowance from a treasury the team controls.

The design goal is not lower fees alone; it is to make a spreadsheet-driven process enforceable. The controls a finance team normally documents in a runbook — separation of duties, no double payment, no silent edits after approval — become properties of the contract.

1. Problem

Paying 200 contractors on-chain today means either 200 individual transfers signed by one person, or a script with a hot key and no review step. Both are operationally weak:

  • No separation of duties. Whoever builds the list also sends the money.
  • No commitment. The list can change between review and execution, and nothing proves what was approved.
  • Weak idempotency. A timed-out run is retried by hand, and someone gets paid twice.
  • Reconciliation by screenshot. There is no export that ties transfers to invoice references and a batch identity.
  • Fee unpredictability. Gas priced in a volatile token makes the cost of a payroll run unquotable.

Arc removes the last problem — USDC is the gas token with stable pricing and deterministic sub-second finality. payrail addresses the other four.

2. Design

Two components: a stateless client that validates and hashes payout files, and PayoutDistributor, an AccessControl + Pausable + ReentrancyGuard contract holding no balance.

PropertyChoiceWhy
CustodyAllowance from treasuryThe contract can spend but never hold. No new honeypot, no migration risk.
ApprovalMaker/checker on-chainThe submitter cannot approve; the rule cannot be bypassed by process drift.
IdentitybatchId = keccak256(label)Human labels map to unique ids; replays are impossible.
IntegritypayloadHash over (token, recipients, amounts)Approval binds to an exact payout set.
SettlementOne transaction per batchAtomic run: either the whole batch settles or nothing does.
AuditEvent per payoutReconciliation without trusting the operator's export.

3. Payload commitment

The client and the contract compute the same hash, so the browser can prove locally what will be accepted on-chain:

payloadHash = keccak256(abi.encode(token, recipients[], amounts[]))

submitBatch(batchId, token, total, recipientCount, payloadHash)
  → status Pending, stores payloadHash and submittedBy

approveBatch(batchId)
  → requires msg.sender != submittedBy, status Approved

executeBatch(batchId, recipients[], amounts[])
  → requires keccak256(abi.encode(...)) == payloadHash
  → status Executed (set before transfers)
  → safeTransferFrom(treasury, recipient, amount) per row

Because status is written before any transfer and Executed is terminal, a reentrant or repeated call cannot pay twice. The recipient list itself is not stored on-chain — only its hash — which keeps calldata cost the dominant term and avoids permanent storage of a payroll roster.

4. Security model

ThreatMitigation
Compromised operator keyCannot pay anyone without an approver signature.
Compromised approver keyCannot submit or execute a payload of its own.
Edited file after approvalPayload hash mismatch reverts execution.
Retry after a timeoutTerminal Executed status makes a batch single-use.
Contract bug or incidentPauser freezes the flow; treasury can revoke the allowance instantly.
Reentrancy via a hostile tokenReentrancyGuard plus status-before-transfer ordering.
Griefing with huge batches500-recipient cap per call.

Residual risks: an admin key that can grant roles and change the treasury (keep it on a Safe), an unlimited allowance if the treasury approves maxUint256 (approve per-batch amounts for tighter control), and the fact that this code is not audited. Ten Foundry tests cover the happy path and each of the reverts above.

5. Costs

Cost per run is one transaction plus one ERC-20 transfer per recipient, priced in USDC. On Arc, fees are stable rather than a function of a volatile gas token, so a payout run can be quoted before it is signed. Splitting a 2,000-row file into four capped batches costs four transactions and four approvals — no per-recipient signing.

6. Limitations

  • Arc only: Arc mainnet is not publicly available, so the app is testnet-first and reads its chain from configuration.
  • Amounts are public on-chain; only references stay off-chain.
  • No scheduling, no fiat on/off ramp, no tax forms — payrail is the settlement leg.
  • Not audited. Treat v0.1 as a testnet reference implementation.

7. Roadmap

PhaseStatusScope
v0.1 — Batch settlement coreShippedCSV upload with address, amount and duplicate validation; Maker/checker approval enforced by the distributor contract; USDC and EURC payouts, up to 500 recipients per transaction; Wallet connect, network switching, role and pause controls; Reconciliation CSV export and browser-tracked batch history.
v0.2 — Treasury controlsIn progressSafe / multisig submission and approval flow; Per-batch allowances instead of a standing approval; Saved recipient books with reference templates; Automatic splitting for files above the 500 recipient cap.
v0.3 — OperationsPlannedREST ingestion and webhooks for payout systems; Scheduled runs for recurring payroll cycles; Role-scoped console views for finance, ops and audit; Accounting exports mapped to ledger entries.
v1.0 — ProductionPlannedExternal audit of the distributor contract; Arc mainnet configuration once a public RPC opens; Optional privacy on payout amounts; CCTP funding from other chains into the treasury.

The console is live on Arc. Start with the docs or open the payout console.