--- eip: 8021 title: Transaction Attribution description: Leverage data suffixes to enables interoperable transaction attribution for apps, wallets, and more. author: Conner Swenberg (@ilikesymmetry), Dan Cortes (@dgca), Ryan Morning (@0xclouds), Nick Prince discussions-to: https://ethereum-magicians.org/t/erc-8021-transaction-attribution/25561 status: Draft type: Standards Track category: ERC created: 2025-09-11 requires: 4337, 5792 --- ## Abstract This ERC defines a standardized method for attributing transactions to applications, wallets, and other entities through structured data suffixes appended to transaction calldata. The standard enables interoperable attribution tracking, supports multiple entity attribution per transaction, and provides a mechanism for reward distribution through registered payout addresses. ## Motivation Currently, applications informally append "data suffixes" to transactions to indicate their origin, typically using the first bytes of a hash representing the application name or domain (e.g. `keccak256("AppName")[0:4]`). This ad-hoc approach presents several limitations: 1. **Non-interoperable attribution**: Each application uses custom suffix formats, requiring explicit checks for specific patterns to detect attributions 2. **Opaque identification**: Hash-based suffixes obscure the actual application identity, making attribution analysis difficult 3. **Reward distribution challenges**: Even when an application is identified, there's no standardized way to determine where to send rewards 4. **Limited multi-entity support**: No clear mechanism for attributing transactions to multiple entities (e.g., both application and wallet) 5. **Lack of extensibility**: No standardized way to query additional metadata about attributed entities This standard addresses these issues by providing a structured, extensible format for transaction attribution that supports multiple entities, reward distribution, and metadata discovery. ## 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. ### Data Suffix Schema This standard defines a structured data suffix format appended to transaction calldata. The suffix consists of the following components (ordered from end of calldata backwards): 1. **`ercMarker`** (16 bytes): A special identifier value indicating use of this standard ("8021" repeated) 2. **`schemaId`** (1 byte): Schema ID number that declares the schema of proceeding bytes with space for forwards compatibility 3. **`schemaData`** (variable bytes): Data to be parsed according to `schemaId` A `schemaId` MUST be defined in an ERC to be considered valid. All multi-byte integer fields in this specification use big-endian byte order. ### Multi-Attribution Schemas Our first schemas enable attributing multiple entities in a single transaction. Entities are identified by a unique "code" which is a string that follows 7-bit ASCII encoding. A code MUST NOT contain commas (`0x2C`) as this is reserved as a delimiter for attributing multiple codes. Codes MUST also be registered in a Code Registry smart contract (interface defined in later section). Chains are encouraged to provide canonical code registries which MAY be defined in this or future ERCs. Schema ID `0` defines the following bytes and assumes chain's canonical Code Registry: 1. **`codesLength`** (1 byte): Length of the codes array in bytes, including delimiters 2. **`codes`** (variable length): Array of attribution codes using ASCII encoding with comma (`0x2C`) as delimiter Schema ID `1` builds on top of `0`, but also defines a custom Code Registry chain and address 1. **`codesLength`** (1 byte): Length of the codes array in bytes, including delimiters 2. **`codes`** (variable length): Array of attribution codes using ASCII encoding with comma (`0x2C`) as delimiter 3. **`codeRegistryChainIdLength`** (1 byte): Length of the chain id hosting the custom Code Registry 4. **`codeRegistryChainId`** (variable bytes): Chain id hosting the custom Code Registry 5. **`codeRegistryAddress`** (20 bytes): Address of a smart contract implementing `ICodeRegistry` Custom Code Registries can be referenced across chains for ease of connecting identity for multi-chain apps. Schema ID `2` uses [CBOR](https://cbor.io/) encoding for extensible transaction annotation: - Optional and unknown fields for future extensions - Coexistence with other suffix-based systems - Arbitrary metadata without requiring new schema IDs 1. **cborLength** (2 bytes): Length of the CBOR-encoded map in bytes 2. **cborData** (variable length): CBOR-encoded map containing attribution data The CBOR map MUST use the following key schema. All fields are OPTIONAL. All top-level keys are reserved for this specification. | Key | Description | | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `a` | Application code - string value | | `w` | Wallet code - string value | | `s` | Service codes - array of string values identifying additional service providers (e.g., block builders, relayers, solvers) | | `r` | Custom registries - sub-map keyed by entity type (`a` for application, `w` for wallet), each containing `c` (chainId as hex string) and `a` (20-byte hex-encoded address string) | | `m` | Custom metadata - sub-map of arbitrary key-value pairs | Only the `m` (metadata) field MAY contain arbitrary data defined by consumers of this specification. All other top-level keys are reserved and MUST NOT be used for purposes other than those defined above. ```typescript type Schema2Data = { a?: string; w?: string; s?: string[]; r?: { a?: { c: Hex; a: Hex }; w?: { c: Hex; a: Hex }; }; m?: Record; }; ``` Note: Schema ID `2` trades minimal byte size for extensibility. Applications prioritizing minimal calldata overhead SHOULD use Schema ID `0` or `1` when their use case does not require extensibility. #### Examples The complete suffix format is: ``` txData||schemaData||schemaId||ercMarker ``` Single entity attribution with canonical registry: ``` txData||"baseapp"||7||0||0x80218021802180218021802180218021 ``` Multiple entity attribution with custom registry: ``` txData||0xCodeRegistry||8453||2||"baseapp,morpho"||14||1||0x80218021802180218021802180218021 ``` Single entity attribution with CBOR (Schema ID 2): ``` {txData}{"a":"baseapp"}{0x000b}{2}{0x80218021802180218021802180218021} ``` Multiple entities with wallet, custom registry, and metadata (Schema ID 2): ``` {txData}{cborData}{0x007b}{2}{0x80218021802180218021802180218021} { "a": "baseapp", "w": "privy", "r": { "a": { "c": "0x2105", "a": "0xBcf2B9598Ec0781eE49230CaACF0FB3C881B5195" } }, "m": { "utm_campaign": "winter-promo", "source": "webapp" } } ``` Multiple entities with services (Schema ID 2): ``` {txData}{cborData}{0x0026}{2}{0x80218021802180218021802180218021} { "a": "baseapp", "w": "privy", "s": ["flashbots", "titan"] } ``` ### Parsing Algorithm Offchain parsers SHOULD implement the following algorithm: 1. Extract the last 16 bytes as `ercMarker`` and verify it matches the standard identifier 2. Extract the previous byte as `schemaId` 3. If `schemaId` is `0``:` a. Extract the previous byte as `codesLength` b. Extract the previous `codesLength` bytes as `codes` c. Implicitly set `codeRegistryAddress` as the registry defined by the host chain d. Parse `codes` using `0x2C` (comma) as delimiter between codes e. Query code-related data from `codeRegistryAddress` on host chain 4. If `schemaId` is `1`: a. Extract the previous byte as `codesLength` b. Extract the previous `codesLength` bytes as `codes` c. Extract the previous byte as `codeRegistryChainIdLength` d. Extract the previous `codeRegistryChainIdLength` bytes as `codeRegistryChainId` e. Extract the previous 20 bytes as `codeRegistryAddress` f. Parse `codes` using `0x2C` (comma) as delimiter between codes g. Query code-related data from `codeRegistryAddress` on chain with id `codeRegistryChainId` 5. If `schemaId` is `2`: a. Extract the previous 2 bytes as `cborLength` b. Extract the previous `cborLength` bytes as `cborData` c. Decode `cborData` as a CBOR map d. Extract application code from key `a` e. Extract wallet code from key `w` (if present) f. Extract service codes from key `s` (if present) as an array of strings g. Extract custom registries from key `r` (if present), containing sub-maps keyed by `a` (application) and/or `w` (wallet), each with `c` (chainId) and `a` (address) h. Extract metadata from key `m` (if present) i. For each code, if no custom registry specified for that entity type, use chain's canonical Code Registry j. Query code-related data from appropriate registry for each code 6. Else `schemaId` does not match, parsing SHOULD terminate (unknown schemaId) ### Code Registry Interface A Code Registry MUST implement the following interface: ```solidity interface ICodeRegistry { /// @notice Fetch a code's address to payout incentives /// @param code The code to query /// @return The address to receive rewards for this code function payoutAddress(string memory code) external view returns (address); /// @notice Fetch a code's URI to fetch for additional metadata /// @param code The code to query /// @return The URI pointing to metadata about this code function codeURI(string memory code) external view returns (string memory); /// @notice Check if a code is valid format (characters, length, etc) /// @dev MUST return false if code is empty string /// @param code The code to query /// @return True if the code is valid, false otherwise function isValidCode(string memory code) external view returns (bool); /// @notice Check if a code is registered /// @param code The code to query /// @return True if the code is registered, false otherwise function isRegistered(string memory code) external view returns (bool); } ``` ### Code Registry Canonical Addresses Schema 0 supports smallest data suffixes by allowing attribution to implicitly use a registry canonically defined by the chain. Chains that offer canonical registries should make their addresses publicly available in their documentation. ### Code Metadata Querying `CodeRegistry.codeURI(code)` returns a string `uri` to fetch actual metadata, following from NFT patterns. The returned metadata SHOULD include these fields: ```typescript type CodeMetadata = { app: { name: string; url: string; }; }; ``` Future ERCs can extend this schema. ### [ERC-4337](./eip-4337.md) User Operation Implementation For ERC-4337 compliant smart accounts, the implementation differs from normal Ethereum transactions: **Normal Transactions**: The attribution suffix is appended to the `data` field of the top-level Ethereum transaction. **ERC-4337 User Operations**: The attribution suffix MUST be appended to the `userOp.callData` field instead of the transaction-level data field, since smart account transactions are executed through the EntryPoint contract where the actual call data is contained within the user operation structure. When implementing attribution for smart accounts: 1. Applications SHOULD append the suffix to `userOp.callData` when constructing user operations 2. Parsers MUST examine the `callData` field of user operations rather than transaction data when processing ERC-4337 transactions 3. Wallets supporting both EOA and smart account transactions MUST handle suffix appending appropriately based on the account type This ensures attribution data remains accessible and parseable regardless of whether the transaction originates from an externally owned account (EOA) or a smart contract account following ERC-4337. ### App<>Wallet Integration Apps can append attribution through multiple methods, but this standard defines integration with two RPCs: 1. `eth_sendTransaction` 2. `wallet_sendCalls` Regardless of integration method, wallets MAY insert their own attribution code alongside the application-defined codes, provided they understand this standard. #### `eth_sendTransaction` Integration This legacy method is supported by all functional Ethereum wallets. Apps can add attribution to their transactions by directly appending the data suffix to the `data` field. Note that wallets MAY mutate the transaction, for example in cases where ERC-4337 Smart Accounts are used and a transaction is actually sent from an ERC-4337 Bundler. #### `wallet_sendCalls` Integration ([ERC-5792](./eip-5792.md)) This more modern alternative to `eth_sendTransaction` also has wide adoption across wallets and provided simpler interfaces for batching transactions, sponsoring gas, and more open-ended capabilities. Applications using ERC-5792's `wallet_sendCalls` RPC method MUST communicate attribution data through a new `dataSuffix` capability: ```typescript interface DataSuffixCapability { dataSuffix: { value: string; // Hex-encoded bytes to append as suffix optional?: bool; // Declare to wallet if adding attribution is optional (reverts if not and wallet does not support) }; } ``` Wallets implementing this capability MUST append the provided `dataSuffix` bytes to the proper data location (transaction data for EOAs, user operation callData for ERC-4337 Smart Accounts). ## Rationale ### Design Decisions **Reverse-order parsing**: The suffix format is designed for parsing from the end of calldata backwards, enabling efficient detection of attribution without requiring knowledge of the original calldata length. **schemaId byte**: Including a schemaId field enables future extensions and optimizations in minimizing attribution data. **Registry pattern**: Using a registry contract allows entities to update their payout addresses and metadata without requiring transaction format changes. **ASCII encoding**: Using human-readable ASCII codes improves debuggability and allows for meaningful code names rather than opaque hashes. **ERC-5792 integration**: The capability-based approach aligns with ERC-5792's extensible design while providing a clean separation of concerns between applications and wallets. **CBOR encoding for Schema ID 2**: Schema IDs 0 and 1 are optimized for minimal bytes but assume sole ownership of the calldata suffix. Schema ID 2 uses CBOR encoding to enable coexistence with other suffix-based systems, support arbitrary metadata, and allow future extensions without requiring new schema IDs. The tradeoff is increased calldata size and parsing complexity. ### Alternative Approaches Considered **Customizable registry**: While a single global registry is recommended when available for reduced transaction data, the flexible registry approach allows different ecosystems to maintain their own attribution systems. **Fixed-length codes**: A fixed-length encoding was considered but rejected to allow for flexible, human-readable identifiers. **[ERC-721](./eip-721.md) dependency**: While codes may be implemented as ERC-721 NFTs, reducing strict dependencies makes this proposal more flexible and clarifies the minimal interface truly required for attribution. **Structured ERC-5792 capability**: A generic bytes `dataSuffix` minimizes the effort on wallets to integrate and maintain support for new schemaIds, increasing chances of wide adoption. ## Forwards Compatibility All schemas after schema V2 must follow the following structure: `{schemaData}{schemaDataLength (2 bytes)}{schemaId (1 byte)}{ercSuffix (16 bytes)}` This layout ensures the complete dataSuffix for any future schema can be deterministically discovered using the same parsing rules. ## Backwards Compatibility This standard is fully backward compatible with existing transaction formats. Transactions without attribution suffixes continue to function normally, and the presence of attribution data does not affect transaction execution. Existing applications using informal attribution methods can migrate to this standard while maintaining their current functionality during the transition period. ## Test Cases Single entity attribution + canonical registry: ``` Input: 0xdddddddd62617365617070070080218021802180218021802180218021 Expected: - txData: 0xdddddddd - schemaId: 0 - codes: ["baseapp"] - registry: canonical ``` Multiple entity attribution + custom registry: ``` Input: 0xddddddddcccccccccccccccccccccccccccccccccccccccc210502626173656170702C6D6F7270686F0E0180218021802180218021802180218021 Expected: - txData: 0xdddddddd - schemaId: 1 - codes: ["baseapp", "morpho"] - codeRegistryChainId: 8453 - codeRegistryAddress: 0xcccccccccccccccccccccccccccccccccccccccc ``` Single entity attribution with CBOR (Schema ID 2): ``` Input: 0xdddddddda161616762617365617070000b0280218021802180218021802180218021 Expected: - txData: 0xdddddddd - schemaId: 2 - cborLength: 0x000b (11 bytes) - cborData: {"a": "baseapp"} ``` Multiple entities with metadata (Schema ID 2): ``` Input: 0xdddddddda46161676261736561707061776570726976796172a16161a26163663078323130356161782a307842636632423935393845633037383165453439323330436141434630464233433838314235313935616da26c75746d5f63616d706169676e6c77696e7465722d70726f6d6f66736f7572636566776562617070007b0280218021802180218021802180218021 Expected: - txData: 0xdddddddd - schemaId: 2 - cborLength: 0x007b (123 bytes) - cborData: {"a":"baseapp","w":"privy","r":{"a":{"c":"0x2105","a":"0xBcf2B9598Ec0781eE49230CaACF0FB3C881B5195"}},"m":{"utm_campaign":"winter-promo","source":"webapp"}} ``` Attribution with services array (Schema ID 2): ``` Input: 0xdddddddda361616762617365617070617765707269767961738269666c617368626f747365746974616e00260280218021802180218021802180218021 Expected: - txData: 0xdddddddd - schemaId: 2 - cborLength: 0x0026 (38 bytes) - cborData: {"a":"baseapp","w":"privy","s":["flashbots","titan"]} ``` Invalid schemaId handling: ``` Input: 0xddddddddff80218021802180218021802180218021 Expected: Parsing stops, unknown schemaId ``` ## Security Considerations ### Parsing Safety As mentioned earlier, we recommend parsers validate the suffix format before processing to prevent malformed data from causing errors. Invalid suffixes can be ignored rather than causing parsing failures. ### Registry Trust The security of attribution data depends on the trustworthiness of the registry contract. Users and applications are encouraged to verify the registry implementation and governance before relying on its data. ### Data Integrity Since attribution data is appended to calldata, it could potentially be modified by intermediaries. Applications requiring cryptographic proof of attribution should implement additional signature-based verification mechanisms. ### Privacy Considerations Attribution data is publicly visible onchain. Future standards can find ways to standardize privacy-preserving attribution, but this it out of scope for the initial proposal and can be added in the future. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).