ERC-6672: Multi-Redeemable NFTs
ERC-6672 generalises ERC-2135 (consumable) to multiple redemption events per NFT, each tracked independently. A single NFT can be redeemed at the bar, then again at the merchandise booth, then again for a backstage pass — each redemption recorded separately by operator + redemptionId. The NFT itself stays in the holder's wallet and is never burned.
Used by:
- Multi-perk membership NFTs — concert ticket → drink + merch + meet-and-greet, each with separate redemption events.
- Achievement / quest NFTs — game NFT records completion of multiple quests, each as a separate redemption.
- Subscription NFTs — monthly redemptions tracked per period.
- Loyalty programs — purchase-discount redemptions per merchant campaign.
Required Interface
interface IERC6672 {
event Redeem(address indexed _operator, uint256 indexed _tokenId,
address redeemer, bytes32 _redemptionId, string _memo);
event Cancel(address indexed _operator, uint256 indexed _tokenId,
bytes32 _redemptionId, string _memo);
function isRedeemed(address _operator, uint256 _tokenId, bytes32 _redemptionId)
external view returns (bool);
function getRedemptionIds(address _operator, uint256 _tokenId)
external view returns (bytes32[] memory);
function redeem(uint256 _tokenId, bytes32 _redemptionId, string calldata _memo) external;
function cancel(uint256 _tokenId, bytes32 _redemptionId, string calldata _memo) external;
}The _operator is the redemption authority (e.g. a venue's contract); the _redemptionId is a unique identifier for the redemption type (e.g. keccak256("free-drink")). Cancellation lets the operator reverse a redemption (e.g. customer returns the merch).
Neo Equivalent: NEP-11 + Per-(tokenId, operator, redemptionId) State
public static void Redeem(ByteString tokenId, ByteString redemptionId, string memo)
{
var operatorAddr = Runtime.CallingScriptHash;
if (!Runtime.CheckWitness(operatorAddr)) throw new Exception("NEP11:NoAuth");
var key = RedemptionKey(operatorAddr, tokenId, redemptionId);
if (Storage.Get(Storage.CurrentContext, key) is not null)
throw new Exception("NEP11:AlreadyRedeemed");
Storage.Put(Storage.CurrentContext, key, 1);
OnRedeem(operatorAddr, tokenId, OwnerOf(tokenId), redemptionId, memo);
}
public static bool IsRedeemed(UInt160 operatorAddr, ByteString tokenId, ByteString redemptionId)
=> Storage.Get(Storage.CurrentContext, RedemptionKey(operatorAddr, tokenId, redemptionId)) is not null;| ERC-6672 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
redeem(tokenId, redemptionId, memo) | Redeem(tokenId, redemptionId, memo) | Operator-witness-checked |
cancel(tokenId, redemptionId, memo) | Cancel(tokenId, redemptionId, memo) | Same operator only |
isRedeemed(operator, tokenId, redemptionId) | IsRedeemed(operator, tokenId, redemptionId) | Direct port |
getRedemptionIds(operator, tokenId) | GetRedemptionIds(operator, tokenId) enumerated via Storage.Find | |
Redeem / Cancel events | OnRedeem / OnCancel notifications |
Comparison With ERC-2135 (Consumable)
| Feature | ERC-2135 | ERC-6672 |
|---|---|---|
| Redemptions per NFT | One (boolean flag) | Many (per operator + redemptionId) |
| Cancel / undo | No (consumed = consumed) | Yes (operator can cancel) |
| Multi-operator | No | Yes (each operator has independent state) |
| Storage cost | O(1) per token | O(operators × redemptions) |
| Use case | Single-use ticket | Multi-perk membership |
ERC-2135 is the lighter choice for one-shot vouchers; ERC-6672 is necessary for multi-event memberships. The Neo storage prefix design makes the cost difference negligible at small scales but grows linearly with redemption count.
Composition
- ERC-2135 — single-use consumable. Use ERC-6672 when multiple redemption events are needed.
- ERC-7432 — roles. The "redeemer" role can delegate redemption authority to a venue without giving away ownership.
- ERC-2981 — royalties. Each redemption can pay a royalty to the issuer.
Migration Notes
For loyalty / membership NFT systems:
- Use
keccak256("redemption-name")(orSha256on Neo) as theredemptionId— gives off-chain tools a deterministic key. - The operator address is the venue / merchant contract; multiple operators per NFT collection are supported by the per-operator storage prefix.
- Renderers (wallet UIs) iterate the redemption prefix to show "Stamps redeemed: X / Y" against the NFT.
For multi-merchant loyalty (e.g. hotel rewards across chains), each brand operates as a separate operator — the NFT's redemption history is a unified view across all of them.
