Skip to main content
Spec: docs/superpowers/specs/2026-08-06-deposit-rail-gas-economics.md §4.

The problem

The keeper fronts native gas for every deposit-rail sweep. Two things make the shipped recovery model untenable. The existing channel is being deleted. Today the keeper recovers gas via the distribute() distributor incentive — a bps fraction of gross, capped by the pool’s committed distributionIncentiveBps. The plan to zero that incentive and restore a true 1% flat removes the only mechanism by which deposit-rail gas is recovered at all. Two issues written independently, in direct conflict. The bps model does not track the cost in either direction. Gas is a fixed cost per invoice; the incentive is a percentage of gross per distribution. At 50 bps on a 20order20 order — 0.10 recovered: Worse, the incentive is charged per distribution, not per invoice. A merchant batching 1,000 small invoices into one distribute pays 50 bps once while the platform paid 1,000 forwarder deploys. TRON needs a $368 order for 50 bps to cover a single invoice.

The mechanism

Take the reimbursement at distribute, not at sweep, and route it through the existing treasury transfer.

Final split

Treasury and partner are computed on the full gross; the gas is carved from the merchant share only. This mirrors how the existing incentive already behaves at MerchantPool.sol:203.
Deducting off the top, before the split is wrong. It spreads the cost pro-rata, silently shrinking the partner’s 0.25% referral on every deposit-rail order. A regression test must pin that partner and treasury shares are unaffected by deposit-rail gas — partner earnings already display as $0.00 in the dashboard, so this would go unnoticed.

bps becomes a ceiling, not a formula

The cap must be min(bps, absolute)

A bps-only cap recreates the exact mismatch this design exists to fix. TRON’s 1.84needs200bpsona1.84 needs 200 bps on a 92 order but 500 bps on a 37one.Thecapisimmutableatpooldeploywhilethemerchantstoleranceisamutablesetting,soitmustbesizedfortheloosesttolerancetheymighteverpickanda500bpscapona37 one. The cap is immutable at pool deploy while the merchant's tolerance is a mutable setting, so it must be sized for the loosest tolerance they might ever pick — and a 500 bps cap on a 10,000 order authorises a $500 claim, which permissionless distribute() makes reachable by anyone.
Both immutable in init code, the absolute term set per chain. Self-limiting rather than lossy: excess debt carries forward.

Recipient is the committed treasury address, never msg.sender

Today’s incentive pays msg.sender — correct for a permissionless incentive, wrong for a gas reimbursement. Once the merchant presses Claim, the merchant is msg.sender and would receive their own gas debt. The recipient must be the treasury address fixed in the init code. This is a semantic change from the deployed contract, not a parameter change.

Why treasury rather than a separate keeper payee

Stablecoin sitting in the keeper address is useless — the keeper needs native gas. Whoever receives the reimbursement, treasury must still run sell-stablecoin, buy-native, fund-keeper. A fourth recipient buys nothing operationally while costing 15,000–30,000 gas per distribution, which the merchant pays as part of their Claim transaction. Accounting stays decomposable: treasury receives gross × treasuryBps / 10_000 + reimbursement, and both components are recoverable off-chain because the bps value is committed and known.

The receipt ledger

The contract never knows about gas. All pricing lives off-chain.
1

Send

Keeper sends the clone-and-sweep transaction.
2

Read the receipt

gasUsed × effectiveGasPrice on EVM, energy_usage_total × sun rate on TRON.
3

Snapshot USD at spend time

Convert via Chainlink and store the snapshot. Do not recompute at distribute — a price move would re-price the debt.
4

Write the row

(invoice, chain, native_spent, usd_at_spend, token_units).
5

Sum at distribute

Total the unreimbursed rows for that pool and token, pass as reimbursement, mark them reimbursed.
6

Carry forward the remainder

Anything the cap rejected stays open for the next distribution.

Carry-forward, never claim-gating

Deduction only works when there is something to deduct from. An underpaid, wrong-token, or never-settled invoice leaves gas spent against a pool with little in it. The answer is to carry the shortfall to the next distribution, still capped. A merchant who keeps trading always repays; one who stops leaves a small unrecovered balance.
A carried debt must never block or delay distribute() or a claim. Blocking a payout is the withholding power described in Distribution — it produces the identical merchant net while handing the platform a capability a regulator will treat as custody.

Second-order effect

Charging the minting merchant for their own invoices’ gas largely defuses the self-dealing griefing vector, where an attacker mints invoices at the floor and pays themselves, recovering 99% while the platform eats the gas. The attack stops being economically interesting when the attacker funds it.

Open commercial decision

Actual-cost billing makes the merchant’s effective rate chain-dependent — 1% plus 0.01onEthereum,10.01 on Ethereum, 1% plus 1.84 on TRON. That collides with a single all-in headline number, and the fee disclosure has already needed one correction. A published flat per-chain surcharge may sell better than a true pass-through even though it over- and under-recovers at the margins. See Open decisions.