--- eip: 8129 title: Non-Transferable Token description: An interface for tokens permanently and structurally bound to a single address, with no transfer function defined at the interface level author: Ivan Zemko (@Vantana1995) discussions-to: https://ethereum-magicians.org/t/erc-8129-non-transferable-token/27407 status: Draft type: Standards Track category: ERC created: 2026-01-19 requires: 165, 721 --- ## Abstract A non-transferable token is bound to a single address for its entire lifetime: once minted, it can never be moved, sold, or reassigned by any party, including its owner. This makes non-transferable tokens suitable for representing credentials, achievements, certifications, memberships, and other identity attestations that only have meaning when tied to the party they were issued to. This EIP defines such tokens by omission rather than restriction: the interface defines minting, burning, and ownership queries, and simply does not define any transfer function. There is no `transferFrom`, no `approve`, and no signature-gated handoff to block or bypass - non-transferability is a property of the interface itself, not a rule enforced on top of a transferable one. ## Motivation Non-transferable tokens represent credentials, achievements, certifications, memberships, and identity attestations - assets that by their nature should be permanently bound to their owner. A university diploma, a proof of attendance, or a revocable membership badge only has meaning if it cannot be sold, gifted, or otherwise separated from the identity it was issued to. These use cases deserve a dedicated standard that explicitly communicates non-transferability through the absence of transfer functions, not their restriction. Current Soulbound token implementations take different approaches but all retain some form of token movement between addresses: - [ERC-5192](./eip-5192.md) and [ERC-5484](./eip-5484.md) extend [ERC-721](./eip-721.md), inheriting its complete transfer infrastructure and then blocking these capabilities - Other approaches avoid implementing ERC-721's transfer interface directly, but still expose signature-gated functions that let a token move between addresses with the recipient's consent Both approaches create several problems: 1. **Semantic Mismatch**: Tokens that should never move between addresses still carry mechanisms for doing so - either through blocked transfer functions (ERC-5192, ERC-5484) or consensual movement (via signature-gated give/take schemes). True soulbound tokens should be permanently bound to their recipient. 2. **Bytecode Bloat**: Standards extending ERC-721 inherit functions like `transferFrom`, `safeTransferFrom`, `approve`, `setApprovalForAll`, `getApproved`, and `isApprovedForAll` in the contract bytecode, even when these functions will never execute successfully. This increases deployment costs and reduces available space for actual token functionality. 3. **Interface Confusion**: External contracts and interfaces may incorrectly assume transfer or movement capabilities exist, leading to integration issues and user confusion. 4. **Not Truly Non-Transferable**: Existing implementations either block transfers through restrictions or allow consensual movement between addresses. In both cases, the token is not fundamentally bound to a single address - it's restricted from free transfer or requires consent to move. ## 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. ### Core Interface Every compliant contract MUST implement the `ERC8129` (Non-Transferable Token) interface: ```solidity pragma solidity ^0.8.0; /// @title ERC-8129 Non-Transferable Token Standard /// @dev See https://eips.ethereum.org/EIPS/eip-8129 /// Note: the ERC-165 identifier for this interface is 0x4ba63534. interface ERC8129 { /// @dev Emitted when a token is minted and bound to an address /// @param to The address the token is bound to /// @param tokenId The identifier of the minted token event Mint(address indexed to, uint256 indexed tokenId); /// @dev Emitted when a token is burned /// @param from The address the token was bound to /// @param tokenId The identifier of the burned token event Burn(address indexed from, uint256 indexed tokenId); /// @notice Mint a new non-transferable token to a specified address /// @dev The token MUST be bound to the address specified in the 'to' parameter /// @param to The address to bind the token to /// @return tokenId The identifier of the newly minted token function mint(address to) external returns (uint256 tokenId); /// @notice Burn a non-transferable token /// @dev MUST revert if msg.sender is not the owner of tokenId /// @param tokenId The identifier of the token to burn function burn(uint256 tokenId) external; /// @notice Get the owner of a token /// @dev MUST revert if tokenId does not exist /// @param tokenId The identifier of the token /// @return owner The address that owns the token function ownerOf(uint256 tokenId) external view returns (address owner); } ``` ### Metadata Interface (Required) Compliant contracts MUST implement ERC-721's [`ERC721Metadata`](./eip-721.md#specification) interface for human-readable token information. See [ERC-721](./eip-721.md) for a definition of its metadata JSON Schema. All compliant contracts MUST implement both `ERC8129` and `IERC721Metadata` interfaces. ### Behavior Specification 1. **Minting**: - Tokens MUST be minted to a specified address via the `to` parameter - The minting function MUST emit the `Mint` event - Each token MUST have a unique identifier - The `to` parameter allows issuers to mint tokens directly to recipients (e.g., educational certificates, membership cards) 2. **Burning**: - Only the token owner MUST be able to burn their token - The burning function MUST emit the `Burn` event - After burning, `ownerOf(uint256 tokenId)` for that token MUST revert 3. **Non-Transferability**: - Compliant contracts MUST NOT implement any transfer functionality - There MUST be no mechanism to change the owner of an existing token - The only way to change token ownership is through burn and re-mint operations by authorized parties 4. **Owner Queries**: - `ownerOf(uint256 tokenId)` MUST return the address that owns the specified token - `ownerOf(uint256 tokenId)` MUST revert for non-existent tokens ### Events - The `Mint` event MUST be emitted whenever a token is created and bound to an address, regardless of which function performs the creation (e.g., batch-minting functions MUST emit one `Mint` event per token created). - The `Burn` event MUST be emitted whenever a token is destroyed and its binding to an address is removed, regardless of which function performs the destruction. - Implementations MUST NOT emit `Mint` or `Burn` events for operations that do not change whether a token exists (e.g., metadata updates). ### [ERC-165](./eip-165.md) Interface Identification Contracts implementing this standard MUST implement the [ERC-165](./eip-165.md) [`supportsInterface`](./eip-165.md#how-a-contract-will-publish-the-interfaces-it-implements) function and MUST return `true` for: - The `IERC8129` interface ID `0x4ba63534` - The `IERC721Metadata` interface ID `0x5b5e139f` - The ERC-165 interface ID `0x01ffc9a7` External contracts MUST NOT assume transfer capabilities based solely on the presence of metadata functions; they MUST rely on [ERC-165](./eip-165.md#how-to-detect-if-a-contract-implements-any-given-interface) interface support to determine capabilities. ## Rationale ### Why Not Extend ERC-721 or Use Consensual Transfer? #### ERC-721 Extensions (ERC-5192, ERC-5484) ERC-721 is architecturally designed around transferability. Every aspect of its interface assumes tokens can move between addresses. Attempting to create non-transferable tokens by blocking these functions creates an architectural mismatch: - Transfer functions exist but always revert - Approval mechanisms serve no purpose - Events like `Transfer` and `Approval` create confusion - Storage slots for approvals and operators are unused #### Consensual Transfer While some approaches avoid implementing ERC-721's transfer interface directly, they still expose `give()`/`take()`-style functions that let tokens move between addresses with signature consent. This creates a fundamental contradiction: - Tokens can still change owners through consensual operations - The `give/take` mechanism contradicts the concept of permanent binding - Adds complexity through signature verification and structured data - The token is "movable with consent" rather than truly soulbound #### This Standard A dedicated standard removes transfer baggage entirely and eliminates any mechanism for tokens to move between addresses, even with consent. Once minted to an address, the token is permanently bound. ### Minimal Interface Design The core interface contains only what is essential for non-transferable tokens: - `mint(address to)`: Create and bind token to specified address - `burn(uint256 tokenId)`: Destroy token - `ownerOf(uint256 tokenId)`: Verify ownership Additionally, the standard REQUIRES ERC-721 Metadata interface (`name()`, `symbol()`, `tokenURI(uint256 tokenId)`) for compatibility with existing wallet and indexer infrastructure while maintaining minimalism. ### Why Include `address to` Parameter in Mint? The `mint(address to)` function allows issuers to directly mint tokens to recipients, which is essential for use cases such as: - Educational institutions issuing diplomas to graduates - Organizations issuing membership credentials - Event organizers issuing attendance certificates - Credential issuers binding attestations to specific addresses This design pattern aligns with real-world issuance scenarios where the issuer (contract caller) is different from the recipient. ### Why Allow Burning? While tokens cannot be transferred, allowing the owner to burn their token provides: - User autonomy to remove unwanted credentials - Mechanisms for credential revocation - Compatibility with credential lifecycle management ### Comparison with Existing Standards | Feature | ERC-721 Extensions (5192, 5484) | Consent-Based Approaches | This Standard | | -------------------------- | -------------------------------- | ------------------------ | ----------------- | | Implements ERC-721 | Yes | No | No | | Transfer functions | Present but blocked | Absent | Absent | | Movement between addresses | Blocked | Via give/take | Impossible | | Approval mechanisms | Present but unused | Absent | Absent | | Deployment bytecode | ~500+ lines | ~300 lines | ~100 lines | | Semantic clarity | Transferable with restrictions | Movable with consent | Permanently bound | | Truly non-transferable | No (logic exists) | No (can move) | Yes | ## Backwards Compatibility This standard is not backwards compatible with ERC-721. This is intentional - tokens that are fundamentally non-transferable should not masquerade as transferable tokens. Wallets and marketplaces can detect this standard via [ERC-165](./eip-165.md#how-to-detect-if-a-contract-implements-any-given-interface) and treat these tokens appropriately (e.g., not showing transfer or listing options). ## Reference Implementation ```solidity // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; import "./IERC8129.sol"; import "./IERC721Metadata.sol"; contract ERC8129 is IERC8129, IERC721Metadata { uint256 private _tokenIdCounter; string private _name; string private _symbol; string private _baseURI; mapping(uint256 => address) private _owners; error NotOwner(); error TokenNotFound(); error InvalidAddress(); constructor(string memory name_, string memory symbol_, string memory baseURI_) { _name = name_; _symbol = symbol_; _baseURI = baseURI_; } function mint(address to) external override returns (uint256) { if (to == address(0)) revert InvalidAddress(); _tokenIdCounter++; uint256 tokenId = _tokenIdCounter; _owners[tokenId] = to; emit Mint(to, tokenId); return tokenId; } function burn(uint256 tokenId) external override { if (_owners[tokenId] != msg.sender) revert NotOwner(); address owner = _owners[tokenId]; delete _owners[tokenId]; emit Burn(owner, tokenId); } function ownerOf(uint256 tokenId) external view override returns (address) { address owner = _owners[tokenId]; if (owner == address(0)) revert TokenNotFound(); return owner; } function name() external view override returns (string memory) { return _name; } function symbol() external view override returns (string memory) { return _symbol; } function tokenURI(uint256 tokenId) external view override returns (string memory) { if (_owners[tokenId] == address(0)) revert TokenNotFound(); return _baseURI; } function supportsInterface(bytes4 interfaceId) external pure returns (bool) { return interfaceId == type(IERC8129).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || interfaceId == 0x01ffc9a7; // ERC-165 } } ``` ## Security Considerations ### Private Key Compromise If a user's private key is compromised, the attacker gains access to all non-transferable tokens bound to that address. Unlike transferable tokens, there is no way to recover these credentials by moving them to a secure address. Mitigation: Applications should consider: - Multi-signature schemes for high-value credentials - Time-locked or conditional burning mechanisms - Off-chain verification in addition to on-chain ownership ### Permanent Binding Tokens are permanently bound to their initial recipient address. If that address becomes inaccessible (lost keys, smart contract bugs), the token cannot be recovered. Mitigation: Consider implementing optional issuer-controlled burning for recovery scenarios, clearly documented in token metadata. ### Minting Authorization The `mint(address to)` function allows minting to any address. Implementations should include appropriate access controls to ensure only authorized parties can mint tokens. ### Interface Detection Because this standard requires the [ERC-721](./eip-721.md) metadata interface, a contract implementing `ERC8129` exposes `name()`, `symbol()`, and `tokenURI()` just like a transferable ERC-721 token would. Integrators that infer transfer capability from the presence of these metadata functions, instead of checking ERC-165 support for the `ERC8129` interface, risk treating a non-transferable token as transferable - for example, listing it on a marketplace or attempting a transfer that will always fail. ### Reentrancy The minimal interface reduces reentrancy attack surface, but implementations should still follow checks-effects-interactions pattern, especially if `mint(address to)` or `burn(uint256 tokenId)` trigger external calls. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).