Skip to main content

value-on-finality

The governing rule: never deliver value or signal completed while the payment is still payer-reversible. Never on first-seen. Zero-confirmation crediting is how iGaming and exchange operators lose money, and reorgs are real. Confirmation depth is value-scaled, with per-chain defaults and per-merchant overrides. Soft and hard finality are distinguished — Ethereum’s finalized checkpoint, TRON’s roughly 19 confirmations, and the soft-versus-hard distinction on L2s are not the same guarantee.
A known gap: the checkout embed’s payment_confirmed event currently waits for the keeper sweep even on the connect rail, where capture is defined as the deposit confirmation. The UX is stricter than the policy requires, which delays the merchant’s completion signal.

No refund mechanism on either rail

Locked 2026-06-18. This was a deliberate removal, not an omission.
  • Connect rail — irreversible once the deposit reaches required confirmations. It never had a refund window.
  • Deposit rail — the forwarder is sweep-only. No refund(), no refundTo, no refundDeadline, no Refunded event, no window errors.

Why the window was removed

The constraint is the refund target, not a timer. A large share of deposit-rail payments originate from exchange withdrawals. Those funds leave a shared omnibus hot wallet, not the customer’s own address. There is no safe on-chain address to refund to.
Never refund to the inbound sender. A CEX-originated deposit returned to its sending address is unattributable and almost always a permanent loss for the customer.
Given that, an on-chain refund window added attack surface and audit scope without protecting the users actually at risk. It also created a free-option exploit: a customer could pay, watch the market, and reverse within the window. Removing it kills that by construction and settles funds faster.

How refunds actually work

Merchant-initiated, off-chain, and manual. The merchant collects a customer-supplied self-custody address at refund time and sends the funds themselves. One path covers every case: overpayment, underpayment, wrong token, and cancellation. The platform never fronts funds and never claws anything back.
Refunds require distribute() and claim() to have run first, because the money is not in the merchant’s hands until then — and by that point the platform fee has already been taken. Whether a refunded order gets its fee returned is an open commercial decision, along with how that is disclosed.

Non-happy paths

The deposit rail has explicit statuses rather than silent failure: underpaid, overpaid, wrong_token, held_sanctioned, expired, expired_paid_late. Each carries confirmed_amount against expected_amount. An underpaid invoice is the sharpest open gap. With refund() removed there is no exit path — the customer’s partial payment sits in a forwarder with no way to complete or return it without manual intervention. The designed answer is a customer top-up prompt plus a merchant sweep-and-refund action.

Reconciliation guarantees

  • Per-checkout discriminator: deposit(paymentId) on the connect rail, a unique address on the deposit rail.
  • Atomic one-to-one claim via UNIQUE(discriminator) and UNIQUE(chain_id, tx_hash, log_index).
  • Reorg-safe: N confirmations plus a finalized cross-check.
  • The destination is verified as the on-chain-committed pool, read from the chain rather than trusted from the database.
Matching by transaction hash, payer, amount, and time window is recovery only — operator-reviewed, never auto-shipping value.