ERC-2135: Consumable Interface
ERC-2135 standardises a tiny extension for "consumable" tokens — event tickets, permits, vouchers, redeemable collectibles. The token records whether it has been consumed; once consumed, it can't be consumed again. The ERC exposes:
interface IERC2135 {
event OnConsumption(address indexed consumer, uint256 indexed assetId, uint256 amount);
/// Returns true if `consumer` is allowed to consume `assetId`.
function isConsumableBy(address consumer, uint256 assetId, uint256 amount)
external view returns (bool);
/// Mark `assetId` consumed by `consumer`. Reverts if not consumable.
function consume(address consumer, uint256 assetId, uint256 amount)
external returns (bool);
}Used by:
- Event ticketing — turnstile scans
consume(holder, ticketId, 1); each ticket is single-use. - Permit / voucher systems — coupon redemption, "claim" mechanics for airdrops, one-shot governance vote tokens.
- NFT-backed redemption — physical merchandise tied to an NFT, where the NFT remains in the wallet but its "redemption" right is consumed.
Critically, the token stays in the holder's wallet after consumption — ERC-2135 doesn't burn the token; it just flips a flag. This is the key distinction from a burn-on-redeem flow (which is also valid but loses the proof-of-attendance / proof-of-claim trace).
Neo Equivalent: NEP-11 + Per-Token Consumed Flag
The Neo equivalent is a NEP-11 with a (tokenId) -> bool consumed flag in storage, plus IsConsumableBy and Consume views/methods that gate on the flag.
public static bool IsConsumableBy(UInt160 consumer, ByteString tokenId, BigInteger amount)
{
if (consumer is null || amount != 1) return false;
if (OwnerOf(tokenId) != consumer) return false;
return !IsConsumed(tokenId);
}
public static bool Consume(UInt160 consumer, ByteString tokenId, BigInteger amount)
{
if (!Runtime.CheckWitness(consumer)) throw new Exception("NEP11:NoAuth");
if (!IsConsumableBy(consumer, tokenId, amount)) throw new Exception("NEP11:NotConsumable");
SetConsumed(tokenId);
OnConsumption(consumer, tokenId, amount);
return true;
}| ERC-2135 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
isConsumableBy(consumer, assetId, amount) | IsConsumableBy(consumer, tokenId, amount) view | Returns false if consumed, not owned, or wrong amount |
consume(consumer, assetId, amount) | Consume(consumer, tokenId, amount) | Gated by witness + consumability check |
OnConsumption event | OnConsumption notification | Same wire shape |
| Token stays in wallet post-consume | Token stays in wallet post-consume | NFT not burned; proof-of-claim preserved |
Pairs Well With
- ERC-4907 — rental NFT. A consumable rental ticket expires per ERC-4907's
userExpiresAND can be consumed mid-rental per ERC-2135. - ERC-5484 — consensual soulbound. Soulbound + consumable = "this credential can be presented once, then it's spent".
- ERC-5192 — minimal soulbound. Same composition.
- ERC-2981 — NFT royalty. Royalties paid on the consume event, not on transfer (relevant for "single-use creator voucher" patterns).
Migration Notes
Solidity contracts using ERC-2135 port cleanly to Neo:
- Replace the consumed mapping (
mapping(uint256 => bool)) withStorage.Put(consumedKey(tokenId), 1)/Storage.Get(...). - Replace the consumer-allowance check with
Runtime.CheckWitness(consumer). - The
OnConsumptionevent becomes a NEP-style notification.
For event ticketing systems, the on-chain footprint is small (one storage write per scanned ticket); the heavy lifting (QR code generation, scanner SDK) lives off-chain and just submits the consume transaction at the turnstile.
