EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8204: Token Payments

Payment signaling for token transfers in commerce applications.

Authors
Created2026-03-19
Discussion Linkhttps://ethereum-magicians.org/t/erc-8204-token-payments/28046

Markdown

https://raw.githubusercontent.com/ethereum/ERCs/re...
Pull Request#1617PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationDefinitionsEventsMetadata Payload EncodingMetadata TypesMetadata Type DiscoveryImplementation ModesNative ModeSettlement ModeInterpretation RulesIdentifier DerivationRationalePayment Event SemanticsMetadata Payload EncodingSettlement ModeBackwards CompatibilityReference ImplementationMetadata Types and Minimal ERC-20 Reference InterfacesMinimal Settlement Emitter ExampleSecurity ConsiderationsCopyright

Abstract

This ERC defines a standard way to signal token payments. It introduces a Payment event for individual payments, an optional Order event for transaction-level metadata, conventions for typed metadata payloads, interfaces and implementation modes for token standards and settler contracts.

These events let applications and indexers distinguish payments from generic transfers, and associate context such as the buyer, and payment or order metadata.

Motivation

Transfer events in existing token standards do not distinguish a payment for goods, services or other expenses from a simple transfer.

Payment-specific data matters for commerce applications such as wallets, checkout systems, merchant tooling, and accounting systems. Indexers could use the additional context to automatically categorize onchain payments, and applications to provide a better user experience to buyers and merchants.

Typically, offchain payment systems offer this distinction as a first-class primitive. On Ethereum, token transfers have generally lacked a standard way to express it.

This ERC introduces a standard payment signal without changing the underlying token transfer semantics.

Specification

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

  • native transfer event: the canonical token movement event defined by the underlying token standard.
  • native transfer arguments: the ordered argument list of the native transfer event, including argument names, types, and order.
  • indexed-argument limit: the maximum number of indexed event arguments permitted for an event. For standard EVM events, this limit is 3.
  • buyer: the intended beneficiary of the goods or services, as asserted by the caller or settler contract.
  • payer: the address funding the payment. When the corresponding transfer semantics identify a source address, payer refers to that source address.
  • recipient: the logical payee represented by a payment integration. It is not necessarily the same as the native transfer to address.
  • currency: the token contract address for the asset represented by the payment entry.
  • payment amount: the asset quantity represented by a Payment entry under the underlying token standard's native transfer semantics. It MAY be scalar or structured; for a transfer of a single non-fungible asset, it is 1.
  • payment data: ABI-encoded payment metadata payloads.
  • order data: ABI-encoded order metadata payloads.

payer and recipient are semantic roles in this ERC. They do not rename native transfer arguments such as from and to, which MUST be preserved where this ERC derives events or functions from an underlying token standard.

Events

For a token standard whose native transfer event is:

event Transfer(T1 a1, T2 a2, ..., Tn an);

the corresponding Payment event MUST be:

event Payment(T1 a1, T2 a2, ..., Tn an, address buyer, address currency, bytes[] data);

Payment represents an individual payment entry.

The leading arguments of Payment MUST match the native transfer event exactly in name, type, and order. buyer, currency, and data MUST be appended in that order. currency and data MUST NOT be indexed.

If the native transfer event uses fewer than the indexed-argument limit, buyer MUST be indexed. Otherwise, if the native transfer event indexes an argument representing the transfer source (payer), that argument MUST NOT be indexed and buyer MUST be indexed. In all other cases, the leading Payment arguments MUST preserve the native transfer event's indexedness.

event Order(address indexed buyer, address indexed payer, bytes[] data);

Order MAY be emitted for metadata that applies to multiple payments in a transaction.

Payment.data and Order.data MUST use the Metadata Payload Encoding defined below.

Metadata Payload Encoding

Payment.data and Order.data are ordered collections of metadata payloads.

The set of valid metadata types is extensible. It is RECOMMENDED to standardize them and their characteristics by publishing them as ERCs. Applications MAY support any set of standard or custom metadata types.

A metadata type is a 4-byte value (bytes4). It MUST be defined as the Solidity function selector of a descriptive function signature, so as to give the type a human-readable name and a standard payload encoding corresponding to the ABI encoding of the function arguments.

Payment.data and Order.data MUST be encoded as bytes[], where each element is exactly one metadata payload. Each payload MUST be the ABI encoding of exactly one metadata function call: the first 4 bytes are the metadata type, and the remaining bytes are the ABI-encoded arguments for that type. Producers MUST NOT concatenate multiple metadata payloads into the same array element.

Payment.data and Order.data with no metadata (an empty metadata list) MUST be considered valid.

Applications and indexers that do not recognize a metadata type MAY ignore that payload entry.

Memo, MerchantReference, PurchaseReference, and URI SHOULD appear at most once in a given metadata collection.

MerchantReference and PurchaseReference are opaque values. Applications and indexers MAY store them without interpretation and MAY apply application-specific conventions or registry lookups when interpretation is needed.

The following metadata types are standardized:

interface CommerceMetadata {
    function Memo(string memory text) external;
    function MerchantReference(bytes32 value) external;
    function PurchaseReference(bytes32 value) external;
    function URI(string memory value) external;
}
  • Memo(string) MAY be used in Payment.data or Order.data for a human-readable description, whether brief or long-form. When it begins with <label>: , indexers MAY treat <label> as the payment label.
  • MerchantReference(bytes32) MAY be used in Payment.data or Order.data for merchant-side business references such as an invoice ID, cart ID, order number, or settlement reference.
  • PurchaseReference(bytes32) MAY be used in Payment.data or Order.data for purchase-side or procurement-side references defined by the payer, buyer, or application.
  • URI(string) MAY be used in Payment.data or Order.data for ipfs://, https:// or other URI-based metadata references.

Metadata Types

Metadata types MAY define additional selectors for custom application-specific metadata and future standardized metadata.

A metadata type payload MUST follow the same encoding rules as the core metadata types above: one ABI-encoded function call per array element.

Metadata types MUST NOT redefine or override the semantics of the core metadata types.

Unknown metadata type selectors MUST be safely ignored by applications and indexers that do not recognize them.

Metadata Type Discovery

Applications MAY publish metadata type definitions through registries, catalogs, or other discovery mechanisms for improved decoding and display.

Such discovery mechanisms are OPTIONAL and out of scope for this ERC.

Implementation Modes

Two implementation modes are defined: native to the token or involving a settler contract.

Native Mode

A token implementation or companion token-standard specification compliant with this ERC MUST expose native payment entry points derived from the underlying token standard's transfer functions.

For a token standard whose native transfer functions are:

function transfer(T1 a1, T2 a2, ..., Tn an) external returns (R);
function transferFrom(U1 b1, U2 b2, ..., Um bn) external returns (S);

the corresponding buyer-defaulting payment functions MUST be:

function pay(T1 a1, T2 a2, ..., Tn an, bytes[] calldata paymentData) external returns (R);
function payFrom(U1 b1, U2 b2, ..., Um bn, bytes[] calldata paymentData) external returns (S);

If an implementation supports explicit buyer, the corresponding buyer-explicit payment functions MUST be:

function pay(T1 a1, T2 a2, ..., Tn an, address buyer, bytes[] calldata paymentData) external returns (R);
function payFrom(U1 b1, U2 b2, ..., Um bn, address buyer, bytes[] calldata paymentData) external returns (S);

The leading arguments of pay and payFrom MUST match the corresponding native transfer function exactly in name, type, order, and data location. In buyer-defaulting variants, paymentData MUST be appended last. In buyer-explicit variants, buyer and paymentData MUST be appended in that order.

Implementations MUST:

  1. perform the token transfer according to the underlying token standard;
  2. emit the native transfer event; and
  3. emit Payment defined according to the rules of this ERC.

currency MUST equal to address(this).

If an implementation does not accept an explicit buyer input, the emitted buyer field in Payment MUST default to the address funding the payment.

Settlement Mode

A settler contract MAY emit Payment while settling a token payment in the same transaction.

The settler MAY represent the logical payment recipient in Payment according to the integration, even if the logical payment recipient differs from the transfer to.

currency MUST equal the token contract address for the asset used in the settlement transfer.

A settler MAY emit multiple Payment events for one transfer event, or use multiple transfer events to settle one Payment event.

Interpretation Rules

For native flows, applications and indexers MAY treat Payment as valid payment signaling for a transfer in the same transaction when the token contract emits both events and the leading Payment arguments and currency match the native transfer event and that token contract's address.

For settlement flows, applications and indexers MUST validate Payment against settlement transfers without requiring argument-by-argument correspondence. A Payment event is valid only if payer corresponds to the transfer source, currency equals the token contract that emitted the settlement transfer event, and the aggregate represented payment amount, interpreted using the underlying token standard's native transfer semantics, does not exceed the settled transfer amount.

Payment does not, by itself, prove final credited balance, withdrawability, refunds, or fulfillment.

Applications and indexers MAY use transfers, traces, or protocol-specific rules for stronger validation. Otherwise, they SHOULD treat the settler contract as the trust source for settlement payment semantics.

Identifier Derivation

Applications and indexers MUST derive canonical identifiers exactly as specified in this section.

For an Order event, the canonical orderId MUST be:

keccak256(abi.encode(
    "erc-8204.order",
    transactionHash,
    buyer,
    payer
))

where transactionHash is the hash of the containing transaction and buyer and payer are the indexed arguments of that Order event.

For a Payment event, the canonical paymentId MUST be:

keccak256(abi.encode(
    "erc-8204.payment",
    transactionHash,
    logIndex
))

where transactionHash is the hash of the containing transaction and logIndex is the log index of that Payment event.

Applications and indexers MUST use the ABI encoding and hashing procedure above for canonical identifier derivation.

A transaction MAY include multiple Payment events and multiple Order events.

If multiple Order events in the same transaction share the same buyer and payer, they MUST derive the same orderId and MUST be interpreted as metadata fragments for the same logical order. Applications and indexers MAY merge those metadata fragments or MAY apply implementation-defined precedence rules, such as preferring the first valid Order.

Rationale

Payment Event Semantics

Payment is intended to signal that a transfer is a payment, not to fully attest to the underlying purchase.

currency remains part of the Payment event even for native token support, despite being redundant there, so that native and settlement emitters share a common appended payment payload and applications can index them uniformly.

Defining Payment as the native transfer event arguments plus appended payment fields makes the event structurally compatible with token standards that already define their own transfer semantics. Indexers can pair Payment with transfer events mechanically from the shared leading arguments, and companion standards do not need to reinterpret shared arguments such as from, to, value, tokenId, or id.

Metadata Payload Encoding

Payment.data and Order.data use bytes[] so producers can compose multiple typed metadata payloads while keeping each payload independently decodable.

Each array element still carries one canonical typed payload. This lets applications compose metadata entries without forcing indexers to parse concatenated or ambiguous payload formats.

The standardized metadata core is intentionally narrow. More specific schemas are left to metadata types, or external documents referenced through URI(...). Leaving those richer semantics outside the core metadata set keeps the base payment signal broadly interoperable.

No core Id metadata type is defined because canonical order and payment identifiers must be derived exactly from transaction context, in order to allow transferability between different applications and indexers.

Settlement Mode

Payment arguments are allowed to not match the arguments of the Transfer used for settlement in order to support settlement flows that route assets through vaults, escrows, claim contracts, or other intermediaries.

Backwards Compatibility

Compatible integrations can be added to existing token standards without changing transfer semantics or requiring changes to unsupported tokens.

Reference Implementation

The following reference interfaces and examples target the ERC-20 version of Payment. They are non-normative and instantiate the generic event definition above for ERC-20.

Metadata Types and Minimal ERC-20 Reference Interfaces

The reference snippet below defines the standard metadata types and helper library, then shows buyer-defaulting and buyer-explicit ERC-20 interfaces. An implementation MAY expose one or both.

The ERC-20 reference interfaces preserve ERC-20 argument names such as from and to; semantically, from is the payer and to is the recipient.

interface IERC20Payments {
    function pay(address to, uint256 amount, bytes[] calldata data) external returns (bool);
    function payFrom(address from, address to, uint256 amount, bytes[] calldata data)
        external
        returns (bool);
}

interface IERC20BuyerPayments {
    function pay(address to, uint256 amount, address buyer, bytes[] calldata data)
        external
        returns (bool);
    function payFrom(address from, address to, uint256 amount, address buyer, bytes[] calldata data)
        external
        returns (bool);
}

contract ERC20Payments is ERC20, IERC20Payments, IERC20BuyerPayments {
    event Payment(
        address indexed from,
        address indexed to,
        uint256 value,
        address indexed buyer,
        address currency,
        bytes[] data
    );

    event Order(address indexed buyer, address indexed payer, bytes[] data);

    function pay(address to, uint256 amount, bytes[] calldata data) external returns (bool) {
        _transfer(msg.sender, to, amount);
        emit Payment(msg.sender, to, amount, msg.sender, address(this), data);
        return true;
    }

    function payFrom(address from, address to, uint256 amount, bytes[] calldata data) external returns (bool) {
        _spendAllowance(from, msg.sender, amount);
        _transfer(from, to, amount);
        emit Payment(from, to, amount, from, address(this), data);
        return true;
    }

    function pay(address to, uint256 amount, address buyer, bytes[] calldata data) external returns (bool) {
        _transfer(msg.sender, to, amount);
        emit Payment(msg.sender, to, amount, buyer, address(this), data);
        return true;
    }

    function payFrom(address from, address to, uint256 amount, address buyer, bytes[] calldata data)
        external
        returns (bool)
    {
        _spendAllowance(from, msg.sender, amount);
        _transfer(from, to, amount);
        emit Payment(from, to, amount, buyer, address(this), data);
        return true;
    }
}

Minimal Settlement Emitter Example

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

interface CommerceMetadataType {
    function Memo(string memory text) external;
    function MerchantReference(bytes32 value) external;
    function PurchaseReference(bytes32 value) external;
    function URI(string memory value) external;
}

library CommerceMetadata {
    function Memo(string memory text) internal pure returns (bytes memory) {
        return abi.encodeCall(CommerceMetadataType.Memo, (text));
    }

    function MerchantReference(bytes32 value) internal pure returns (bytes memory) {
        return abi.encodeCall(CommerceMetadataType.MerchantReference, (value));
    }

    function PurchaseReference(bytes32 value) internal pure returns (bytes memory) {
        return abi.encodeCall(CommerceMetadataType.PurchaseReference, (value));
    }

    function URI(string memory value) internal pure returns (bytes memory) {
        return abi.encodeCall(CommerceMetadataType.URI, (value));
    }
}

contract ERC20SettlementEmitter {
    address public immutable settlementTarget;

    constructor(address settlementTarget_) {
        settlementTarget = settlementTarget_;
    }

    event Payment(
        address indexed from,
        address indexed to,
        uint256 value,
        address indexed buyer,
        address currency,
        bytes[] data
    );

    function payWithToken(
        address buyer,
        address token,
        uint256 amount,
        string calldata memo
    ) external {
        require(IERC20(token).transferFrom(msg.sender, settlementTarget, amount), "TRANSFER_FAILED");
        bytes[] memory data = new bytes[](1);
        data[0] = CommerceMetadata.Memo(memo);
        emit Payment(msg.sender, settlementTarget, amount, buyer, token, data);
    }
}

Security Considerations

Applications and indexers MUST NOT treat a settlement Payment event emitted by a settler contract as self-authenticating.

Applications and indexers MUST treat buyer, Payment.data, and Order.data as asserted by the emitting contract, caller, or settler according to the integration, not as self-authenticating proof.

Applications and indexers MUST NOT infer buyer authorization or buyer consent from buyer alone.

Applications and indexers MUST NOT treat Payment.data or Order.data as self-authenticating proof of merchant acceptance, pricing correctness, itemization correctness, tax determination, or fulfillment.

In settlement flows, applications SHOULD treat the settler contract as a trust input and SHOULD NOT assume that Payment alone captures the full business semantics of settlement or final recipient attribution.

Applications SHOULD validate or sanitize decoded Payment.data and Order.data payloads before display or interpretation.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth