Consumable Interface (Tickets, etc)
Abstract
This EIP defines an interface to mark a digital asset as “consumable” and to react to its “consumption.”
Motivation
Digital assets sometimes need to be consumed. One of the most common examples is a concert ticket. It is “consumed” when the ticket-holder enters the concert hall.
Having a standard interface enables interoperability for services, clients, UI, and inter-contract functionalities on top of this use-case.
Specification
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
- Any compliant contract MUST implement the following interface:
pragma solidity >=0.7.0 <0.9.0;
/// The ERC-165 identifier of this interface is 0xdd691946
interface IERC2135 {
/// @notice The consume function consumes a token every time it succeeds.
/// @param _consumer the address of consumer of this token. It doesn't have
/// to be the EOA or contract Account that initiates the TX.
/// @param _assetId the NFT asset being consumed
/// @param _data extra data passed in for consume for extra message
/// or future extension.
function consume(
address _consumer,
uint256 _assetId,
uint256 _amount,
bytes calldata _data
) external returns (bool _success);
/// @notice The interface to check whether an asset is consumable.
/// @param _consumer the address of consumer of this token. It doesn't have
/// to be the EOA or contract Account that initiates the TX.
/// @param _assetId the NFT asset being consumed.
/// @param _amount the amount of the asset being consumed.
function isConsumableBy(
address _consumer,
uint256 _assetId,
uint256 _amount
) external view returns (bool _consumable);
/// @notice The event emitted when there is a successful consumption.
/// @param consumer the address of consumer of this token. It doesn't have
/// to be the EOA or contract Account that initiates the TX.
/// @param assetId the NFT asset being consumed
/// @param amount the amount of the asset being consumed.
/// @param data extra data passed in for consume for extra message
/// or future extension.
event OnConsumption(
address indexed consumer,
uint256 indexed assetId,
uint256 amount,
bytes data
);
}
-
If the compliant contract is an ERC-721 or ERC-1155 token, in addition to
OnConsumption
, it MUST also emit theTransfer
/TransferSingle
event (as applicable) as if a token has been transferred from the current holder to the zero address if the call toconsume
method succeeds. -
supportsInterface(0xdd691946)
MUST returntrue
for any compliant contract, as per ERC-165.
Rationale
- The function
consume
performs the consume action. This EIP does not assume:
- who has the power to perform consumption
- under what condition consumption can occur
It does, however, assume the asset can be identified in a uint256
asset id as in the parameter. A design convention and compatibility consideration is put in place to follow the ERC-721 pattern.
-
The event notifies subscribers whoever are interested to learn an asset is being consumed.
-
To keep it simple, this standard intentionally contains no functions or events related to the creation of a consumable asset. This is because the creation of a consumable asset will need to make assumptions about the nature of an actual use-case. If there are common use-cases for creation, another follow up standard can be created.
-
Metadata associated to the consumables is not included the standard. If necessary, related metadata can be created with a separate metadata extension interface like
ERC721Metadata
from ERC-721 -
We choose to include an
address consumer
forconsume
function andisConsumableBy
so that an NFT MAY be consumed for someone other than the transaction initiator. -
We choose to include an extra
_data
field for future extension, such as adding crypto endorsements. -
We explicitly stay opinion-less about whether ERC-721 or ERC-1155 shall be required because while we design this EIP with ERC-721 and ERC-1155 in mind mostly, we don’t want to rule out the potential future case someone use a different token standard or use it in different use cases.
-
The boolean view function of
isConsumableBy
can be used to check whether an asset is consumable by the_consumer
.
Backwards Compatibility
This interface is designed to be compatible with ERC-721 and NFT of ERC-1155. It can be tweaked to used for ERC-20, ERC-777 and Fungible Token of ERC-1155.
Test Cases
describe("Consumption", function () {
it("Should consume when minted", async function () {
const fakeTokenId = "0x1234";
const { contract, addr1 } = await loadFixture(deployFixture);
await contract.safeMint(addr1.address, fakeTokenId);
expect(await contract.balanceOf(addr1.address)).to.equal(1);
expect(await contract.ownerOf(fakeTokenId)).to.equal(addr1.address);
expect(await contract.isConsumableBy(addr1.address, fakeTokenId, 1)).to.be.true;
const tx = await contract.consume(addr1.address, fakeTokenId, 1, []);
const receipt = await tx.wait();
const events = receipt.events.filter((x: any) => { return x.event == "OnConsumption" });
expect(events.length).to.equal(1);
expect(events[0].args.consumer).to.equal(addr1.address);
expect(events[0].args.assetId).to.equal(fakeTokenId);
expect(events[0].args.amount).to.equal(1);
expect(await contract.balanceOf(addr1.address)).to.equal(0);
await expect(contract.ownerOf(fakeTokenId))
.to.be.rejectedWith('ERC721: invalid token ID');
await expect(contract.isConsumableBy(addr1.address, fakeTokenId, 1))
.to.be.rejectedWith('ERC721: invalid token ID');
});
});
describe("EIP-165 Identifier", function () {
it("Should match", async function () {
const { contract } = await loadFixture(deployFixture);
expect(await contract.get165()).to.equal("0xdd691946");
expect(await contract.supportsInterface("0xdd691946")).to.be.true;
});
});
Reference Implementation
A deployment of version 0x1002 has been deployed onto goerli
testnet at address 0x3682bcD67b8A5c0257Ab163a226fBe07BF46379B
.
Find the reference contract verified source code on Etherscan’s
goerli
site for the address above.
Security Considerations
Compliant contracts should pay attention to the balance change when a token is consumed. When the contract is being paused, or the user is being restricted from transferring a token, the consumeability should be consistent with the transferral restriction.
Compliant contracts should also carefully define access control, particularly whether any EOA or contract account may or may not initiate a consume
method in their own use case.
Security audits and tests should be used to verify that the access control to the consume
function behaves as expected.
Copyright
Copyright and related rights waived via CC0.