ERC-7699 - ERC-20 Transfer Reference Extension

Created 2024-04-26
Status Draft
Category ERC
Type Standards Track
Authors
Requires

Abstract

The ERC-20 token standard does not provide a built-in mechanism for including a payment transfer reference (message for recipient) in token transfers. This proposal extends the existing ERC-20 token standard by adding minimal methods to include a transfer reference in token transfers and transferFrom operations. The addition of a reference can help users, merchants, and service providers to associate and reconcile individual transactions with specific orders or invoices.

Motivation

The primary motivation for this proposal is to improve the functionality of the ERC-20 token standard by providing a mechanism for including a payment reference in token transfers, similar to the traditional finance systems where payment references are commonly used to associate and reconcile transactions with specific orders, invoices or other financial records.

Currently, users and merchants who want to include a payment reference in their transactions must rely on off chain external systems or custom payment proxy implementations. In traditional finance systems, payment references are often included in wire transfers and other types of electronic payments, making it easy for users and merchants to manage and reconcile their transactions. Such as:

By extending the existing ERC-20 token standard with payment transfer reference capabilities, this proposal will help bridge the gap between traditional finance systems and the world of decentralized finance, providing a more seamless experience for users, merchants, and service providers alike.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Any contract complying with ERC-20 when extended with this ERC, MUST implement the following interface:

// The EIP-165 identifier of this interface is 0x1522573a

interface IERC7699 {

function transfer(address to, uint256 amount, bytes calldata transferReference) external returns (bool);

function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference) external returns (bool);

event TransferReference(bytes32 indexed loggedReference);

}

These transfer and transferFrom functions, in addition to the standard transfer behaviour, MUST emit a transferReference event with a loggedReference parameter (with only exception defined below).

The corresponding ERC-20 Transfer event MUST be emitted following the TransferReference event, ideally immediately afterwards for the client to be able to seek the associated Transfer event log record.

Emitted loggedReference MAY be the exact copy of the transferReference (when less then 33 bytes) or the derived data from the rich transferReference structure and other processing. This is up to the implementer. One MUST NOT expect the transferReference and loggedReference data to be equal.

The loggedReference parameter MAY contain any data of bytes32 type.

The transferReference parameter MAY be empty. In this case and only in this case the TransferReference event MUST NOT be emitted, effectively mimicking the regular ERC-20 transfer without any transfer reference.

The transferReference parameter is not limited in length by design, users are motivated to keep it short due to calldata and execution gas costs.

The TransferReference event MUST NOT be declared with the anonymous specifier. This is to ensure that the event signature is logged and can be used as a filter.

Transfers of 0 amount MUST be treated as normal transfers and fire the TransferReference event alike.

Rationale

Parameter name

The choice to name the added parameter transferReference was made to align with traditional banking terminology, where payment references are widely used to associate and reconcile transactions with specific orders, invoices or other financial records.

The transferReference parameter name also helps to clearly communicate the purpose of the parameter and its role in facilitating the association and reconciliation of transactions. By adopting terminology that is well-established in the financial industry, the proposal aims to foster a greater understanding and adoption of the extended ERC-20 token standard.

Parameter type

The transferReference type is bytes.

The transferReference type was initially considered to be bytes32 in order to motivate users to either use short references (as is common in TradFi) or rather use Keccak 256 hash of the reference content. Conclusion was that the options should rather be kept open to be able to call with structured data; such as passing reference data including a signature enabling extra processing checks.

Emitted data

The loggedReference type is bytes32.

It was considered to log a reference in the form of bytes calldata. However, the reference content would be hashed in the event log due to the log topic indexing required for the even subscription filters. The resulting logged topic is always in the form of bytes32. Bytes32 type enables to log publicly readable (non hashed) reference content up to 32 bytes long.

Backwards Compatibility

This extension is fully backwards compatible with the existing ERC-20 token standard. The new functions can be used alongside the existing transfer and transferFrom functions. Existing upgradable ERC-20 tokens can be upgraded to include the new functionality without impact on the storage layout; new ERC-20 tokens can choose to implement the payment reference features based on their specific needs.

Reference Implementation

// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.4 <0.9.0;

import {ERC20} from "@openzeppelin/token/ERC20/ERC20.sol";

interface IERC7699 {
    /**
     * @notice Emitted when a non-empty `transferReference` is added to the `transfer` call.
     */
    event TransferReference(bytes32 indexed loggedReference);

    /**
     * @notice Moves `amount` tokens from the caller's account to `to` with `transferReference`.
     *
     * @dev Returns a boolean value indicating whether the operation succeeded.
     *
     * MUST emit this ERCS's {TransferReference} event followed by a corresponding {ERC20.Transfer} event
     * (to comply with ERC-20).
     */
    function transfer(address to, uint256 amount, bytes calldata transferReference) external returns (bool);

    /**
     * @notice Moves `amount` tokens from `from` to `to` with `transferReference` using the
     * allowance mechanism. `amount` is then deducted from the caller's allowance.
     *
     * @dev Returns a boolean value indicating whether the operation succeeded.
     *
     * MUST emit this ERCS's {TransferReference} event followed by a corresponding {ERC20.Transfer} event
     * (to comply with ERC-20).
     */
    function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference)
        external
        returns (bool);
}

/**
 * @dev Implementation of the ERC20 transfer reference extension.
 */
contract ERC20TransferReference is ERC20, IERC7699 {
    constructor() ERC20("ERC20 Transfer Reference Example", "TXRE") {
        _mint(msg.sender, 987654321 * 1e18);
    }

    /**
     * @dev Emits `TransferReference` event with derived `loggedReference` data
     */
    function _logReference(bytes calldata transferReference) internal virtual {
        // MUST NOT emit when transferReference is empty
        if (transferReference.length > 0) {
            // Effectively extract first 32 bytes from transferReference calldata bytes
            // Note: This is the example. Derivation of the loggedReference is fully up to the implementation.
            // E.g. keccak hash of the whole transferReference, etc.
            bytes32 loggedReference;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                loggedReference := calldataload(transferReference.offset)
            }

            emit TransferReference(loggedReference);
        }
    }

    /**
     * @notice A standard ERC20 token transfer with an optional transfer reference
     * @dev The underlying `transfer` function is assumed to handle the actual token transfer logic.
     * @param to The address of the recipient where the tokens will be sent.
     * @param amount The number of tokens to be transferred.
     * @param transferReference A bytes field to include a transfer reference, reference signature or other relevant
     * reference data.
     * @return A boolean indicating whether the transfer was successful.
     */
    function transfer(address to, uint256 amount, bytes calldata transferReference) public virtual returns (bool) {
        _logReference(transferReference);

        // ERC20.Transfer event is emitted immediately after TransferReference event
        return transfer(to, amount);
    }

    /**
     * @notice A delegated token transfer with an optional transfer reference
     * @dev Requires prior approval from the token owner. The underlying `transferFrom` function is assumed to handle
     * allowance and transfer logic.
     * @param from The address of the token owner who has authorized the transfer.
     * @param to The address of the recipient where the tokens will be sent.
     * @param amount The number of tokens to be transferred.
     * @param transferReference A bytes field to include a transfer reference, reference signature or other relevant
     * reference data.
     * @return A boolean indicating whether the transfer was successful.
     */
    function transferFrom(address from, address to, uint256 amount, bytes calldata transferReference)
        public
        virtual
        returns (bool)
    {
        _logReference(transferReference);

        // ERC20.Transfer event is emitted immediately after TransferReference event
        return transferFrom(from, to, amount);
    }
}

Security Considerations

Privacy Considerations

Reference data privacy: Including payment references in token transfers may expose sensitive information about the transaction or the parties involved. Implementers and users should carefully consider the privacy implications and ensure that payment references do not reveal sensitive information. To mitigate this risk, implementers can consider using encryption or other privacy-enhancing techniques to protect payment reference data.

Example: With reference 0x20240002 logged, transaction is publicly exposing that this is related to the second invoice of the recipient in 2024.

Manipulation of payment references

There is no validation of the reference data dictated by this ERC. Malicious actors might attempt to manipulate payment references to mislead users, merchants, or service providers. This can lead to:

  1. Legal risks: The beneficiary may face legal and compliance risks if the attacker uses illicit funds, potentially impersonating or flagging the beneficiary of involvement in money laundering or other illicit activities.

  2. Disputes and refunds: The user might discover they didn't make the payment, request a refund or raise a dispute, causing additional administrative work for the beneficiary.

To mitigate this risk, implementers can consider using methods to identify proper sender and to generate unique and verifiable related payment references. However such implementations are not in the scope of this standard and rather extend it.

Copyright

Copyright and related rights waived via CC0.