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 and ERC-5484 extend ERC-721, 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:
-
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.
-
Bytecode Bloat: Standards extending ERC-721 inherit functions like
transferFrom,safeTransferFrom,approve,setApprovalForAll,getApproved, andisApprovedForAllin the contract bytecode, even when these functions will never execute successfully. This increases deployment costs and reduces available space for actual token functionality. -
Interface Confusion: External contracts and interfaces may incorrectly assume transfer or movement capabilities exist, leading to integration issues and user confusion.
-
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:
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 interface for human-readable token information.
See ERC-721 for a definition of its metadata JSON Schema.
All compliant contracts MUST implement both ERC8129 and IERC721Metadata interfaces.
Behavior Specification
-
Minting:
- Tokens MUST be minted to a specified address via the
toparameter - The minting function MUST emit the
Mintevent - Each token MUST have a unique identifier
- The
toparameter allows issuers to mint tokens directly to recipients (e.g., educational certificates, membership cards)
- Tokens MUST be minted to a specified address via the
-
Burning:
- Only the token owner MUST be able to burn their token
- The burning function MUST emit the
Burnevent - After burning,
ownerOf(uint256 tokenId)for that token MUST revert
-
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
-
Owner Queries:
ownerOf(uint256 tokenId)MUST return the address that owns the specified tokenownerOf(uint256 tokenId)MUST revert for non-existent tokens
Events
- The
Mintevent 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 oneMintevent per token created). - The
Burnevent 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
MintorBurnevents for operations that do not change whether a token exists (e.g., metadata updates).
ERC-165 Interface Identification
Contracts implementing this standard MUST implement the ERC-165 supportsInterface function and MUST return true for:
- The
IERC8129interface ID0x4ba63534 - The
IERC721Metadatainterface ID0x5b5e139f - 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 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
TransferandApprovalcreate 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/takemechanism 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 addressburn(uint256 tokenId): Destroy tokenownerOf(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 and treat these tokens appropriately (e.g., not showing transfer or listing options).
Reference Implementation
// 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 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.
