ERC-8017: Payout Race

Minimal ERC for a single-asset payout bucket that vends its entire balance for a fixed payment amount.


Metadata
Status: DraftStandards Track: ERCCreated: 2025-08-31
Authors
Kyle Thornton (@kyle) (kyle@cowrie.io)
Requires

Abstract


This ERC specifies a small contract surface for a "payout race": a bucket that holds a single payout asset type and transfers the entire bucket to a recipient when a caller pays a fixed required payment in a configured desired payment asset. The desired payment asset can be ETH or one ERC-20. The payout asset can be ETH or one ERC-20.

This ERC is inspired by the Uniswap Foundation's Unistaker proposal, which introduced the term Payout Race and motivated this design.

Motivation


Many protocols need an ongoing way to convert a continuous stream of value into another asset at or near prevailing market prices. Typical cases include buying back a protocol token using protocol revenue, accumulating a reserve asset, funding incentive budgets, or rebalancing treasuries. Existing patterns have material drawbacks. Integrating an AMM couples outcomes to external liquidity, slippage, and fees, and requires retuning when pool conditions change. General on-chain auctions add operational complexity and higher gas, especially when run continuously.

This ERC defines a deterministic, revenue-driven primitive that is analogous to a Dutch auction. Sources of value flow into this contract, filling a "bucket" of purchasable assets. The first caller that supplies the required payment in the desired payment asset receives the entire current balance of the payout token in the bucket. The interface is small, auditable, and easy to compose with upstream controllers that decide when the exchange is economically sound.

Specification


The following interface and rules are normative. The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Definitions

  • Conforming contract: Any smart contract that exposes this interface and claims compliance with this ERC. This includes proxies and clones. Requirements in this document apply to the observable runtime behavior of the deployed contract.

  • Payout asset: Asset dispensed from the bucket. payoutAsset == address(0) means ETH payout.

  • Desired payment asset: Asset the buyer must pay. Referred to as desiredAsset in the interface. desiredAsset == address(0) means ETH payment.

  • Required payment: Fixed amount of the desired payment asset (desiredAsset) or ETH that must be provided by the buyer to trigger the payout.

Interface


Required Behavior

  1. Exact required payment. Callers MUST provide exactly requiredPayment() in the configured desiredAsset or in ETH to call purchase.

  2. Token pairing. payoutAsset and desiredAsset MUST NOT both be address(0). ETH on both sides is disallowed.

  3. Desired payment asset immutability. desiredAsset MUST NOT change after initialization. A conforming contract MUST NOT expose any callable setter that can change desiredAsset.

  4. Payout asset immutability. payoutAsset MUST NOT change after initialization. A conforming contract MUST NOT expose any callable setter that can change payoutAsset.

  5. All-or-nothing dispense. On purchase, a conforming contract MUST compute the amount to dispense as the live balance of the payout token captured at function entry, before any external calls. The contract MUST transfer exactly this amount to to in a single call and the call MUST revert if this amount is zero.

  6. Payment collection.

    • If desiredAsset == address(0), purchase MUST require msg.value == requiredPayment() and MUST forward that ETH to paymentSink().
    • If desiredAsset != address(0), purchase MUST require msg.value == 0 and MUST call transferFrom(msg.sender, paymentSink(), requiredPayment()) on desiredAsset.
  7. Admin changes. A conforming contract MUST restrict the admin setters to an authorized role and MUST emit PaymentConfigUpdated when requiredPayment or paymentSink change.

Optional Extensions

  • Permit for payment: A conforming contract MAY expose purchaseWithPermit(...) that accepts EIP-2612 permit parameters. If implemented, the function MUST require desiredAsset != address(0), MUST call permit on desiredAsset with the supplied signature, and MUST collect requiredPayment via transferFrom in the same transaction. The call MUST revert if desiredAsset does not implement EIP-2612 or if the permit does not yield sufficient allowance.
  • Rescue for unintended assets: A conforming contract MAY implement an admin-only rescue function to recover assets that are not the payoutAsset (e.g., unsolicited ERC-20s or ETH sent when payoutAsset is an ERC-20). If provided, the function MUST NOT transfer the payoutAsset, MUST emit a Rescued(address token, address to, uint256 amount) event, and MUST be restricted to an authorized role.

Rationale


  • A single required payment pairs well with controllers that evaluate when the exchange is economically sound and trigger purchase only when conditions justify it. The onchain primitive then validates the payment and atomically transfers the entire bucket.
  • paymentSink reduces persistent balances in the contract and simplifies audits. Sinks can be treasuries, splitters, or burns.
  • Using the live onchain balance as the source of truth automatically captures rebases and fee-on-transfer mechanics, and keeps the onchain tracking minimized. It also implies that unsolicited transfers to the contract will be included in the next payout, which purchasers may want to account for at the integration level.

Admin Considerations

Access control for admin setters is intentionally unspecified; EIP-173 ownership or a role-based pattern is recommended.

Some deployments may renounce or restrict admin rights for policy or compliance reasons (for example, renouncing ownership or disabling roles). This ERC does not prescribe any specific mechanism.

The reference uses EIP-173 style ownership for illustration. Any access control that enforces the Required behavior is acceptable. Deployments may assign distinct roles per setter or make one or more parameters immutable. The specification is agnostic to the mechanism.

Parameter Selection and Degenerate Cases

This mechanism works best when value accrues gradually. Large, lumpy deposits can overshoot the required payment threshold and leak value to the first successful caller. Operators should size requiredPayment relative to observed inflow volatility and adjust conservatively. If the payout asset appreciates against the desired payment asset, purchases may stall. If it depreciates, purchases may trigger so frequently that value is lost whenever a large trade pushes the bucket well above the threshold.

Changing requiredPayment carries risks. Lowering it can leak value at the moment of change if accrued payout already exceeds the new threshold, since searchers can win a bargain. Raising it can disrupt or bankrupt naive searchers and MEV bots that provide rewards by arbitraging fee collection. Mitigations may include timelocked or scheduled parameter changes, announce windows, caps on per-block deposits, cooldowns after changes, and time-weighted average pricing (TWAP)-based or ratcheted adjustments to requiredPayment.

Considered Alternatives: Multi-Asset Sweep

This design could be extended to support multiple payout assets by maintaining an explicit allowlist and, on a successful purchase, sweeping each allowlisted token to the recipient using the same mechanics as the single-asset case.

Backwards Compatibility


Compatible with any ERC-20. Wallets and dApps can integrate using standard allowance flows or optional permit helpers.

Reference Implementation



Security Considerations


  • Payout accounting. The dispensed amount is computed from the live onchain balance of the payout asset. Because ETH-to-ETH is disallowed, there is no ambiguity about subtracting msg.value. Capture the amount to dispense at function entry and use that value for the transfer.

  • Reentrancy and external calls. Use the Checks-Effects-Interactions pattern and a reentrancy guard. Avoid any external calls before you (a) capture the amount to dispense and (b) forward payment to paymentSink. Do not perform callbacks between collecting payment and completing the payout.

  • Receiver constraints. The recipient to must be able to receive the asset being dispensed. ETH payouts require a payable fallback; ERC-20 payouts require that to is not a contract that reverts on transfer.

  • Payment sink constraints. The paymentSink must be able to receive the desired payment asset. For ETH payments, paymentSink must be payable. For ERC-20 payments, paymentSink must not revert when credited via transferFrom. Using a burn address, splitter, or treasury is acceptable; the specification is agnostic to the mechanism.

  • Unsolicited transfers. The next payout will include any assets pushed to the contract (e.g., direct ETH sends or ERC-20 transfers). Operators should account for this at the integration layer, or front the contract with filters if needed. An optional admin-only rescue for non-payoutAsset assets can mitigate mistakes without affecting conformance.

  • Approvals and permits. When using ERC-20 payments, callers should consider allowance race conditions. If a purchaseWithPermit helper is implemented, verify domain separator, deadline, and nonce handling, and revert on insufficient post‑permit allowance.

  • Admin changes. Because setters can change requiredPayment or paymentSink, governance should protect these operations. Common mitigations include timelocks, scheduled changes with announcement windows, and immutability for parameters that should never change.

  • Proxies and clones. Constructors do not run per proxy or minimal clone. Implementations should set payoutAsset and desiredAsset once during initialization and ensure they cannot change afterward. Avoid exposing setters and protect initializers against re-entry or multiple calls.

Copyright


Copyright and related rights waived via CC0.