-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
649 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
--- | ||
eip: 7498 | ||
title: NFT Redeemables | ||
description: Extension to ERC-721 and ERC-1155 for onchain and offchain redeemables | ||
author: Ryan Ghods (@ryanio), 0age (@0age), Adam Montgomery (@montasaurus), Stephan Min (@stephankmin) | ||
discussions-to: https://ethereum-magicians.org/t/erc-7498-nft-redeemables/15485 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2023-07-28 | ||
requires: 165, 712, 721, 1155, 1271 | ||
--- | ||
|
||
## Abstract | ||
|
||
This specification introduces a new interface that extends [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) to enable the discovery and use of onchain and offchain redeemables for NFTs. | ||
|
||
## Motivation | ||
|
||
Creators frequently use NFTs to create redeemable entitlements for digital and physical goods. However, without a standard interface, it is challenging for users and apps to discover and interact with these NFTs in a predictable and standard way. This standard aims to encompass enabling broad functionality for: | ||
|
||
- discovery: events and getters that provide information about the requirements of a redemption campaign | ||
- onchain: token mints with context of items spent | ||
- offchain: the ability to associate with ecommerce orders (through `redemptionHash`) | ||
- trait redemptions: improving the burn-to-redeem experience with E RC-7496 <!-- [E RC-7496] (./e ip-7496.md) --> Dynamic Traits. | ||
|
||
## 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. | ||
|
||
The token MUST have the following interface and MUST return `true` for [ERC-165](./eip-165.md) supportsInterface for `0x12345678(placeholder, to be set here when finalized)`, the 4 byte interfaceId of the below. | ||
|
||
```solidity | ||
interface IERC7501 { | ||
/* Events */ | ||
event CampaignUpdated(uint256 indexed campaignId, CampaignParams params, string URI); | ||
event Redemption(uint256 indexed campaignId, uint256 requirementsIndex, bytes32 redemptionHash, uint256[] considerationTokenIds, uint256[] traitRedemptionTokenIds, address redeemedBy); | ||
/* Structs */ | ||
struct CampaignParams { | ||
uint32 startTime; | ||
uint32 endTime; | ||
uint32 maxCampaignRedemptions; | ||
address manager; // the address that can modify the campaign | ||
address signer; // null address means no EIP-712 signature required | ||
CampaignRequirements[] requirements; // one requirement must be fully satisfied for a successful redemption | ||
} | ||
struct Campaign { | ||
OfferItem[] offer; // items to be minted, can be empty for offchain redeemable | ||
ConsiderationItem[] consideration; // items transferring to recipient | ||
TraitRedemption[] traitRedemptions; // the trait redemptions to process | ||
} | ||
struct TraitRedemption { | ||
uint8 substandard; | ||
address token; | ||
uint256 identifier; | ||
bytes32 traitKey; | ||
bytes32 traitValue; | ||
bytes32 substandardValue; | ||
} | ||
/* Getters */ | ||
function getCampaign(uint256 campaignId) external view returns (CampaignParams memory params, string memory uri, uint256 totalRedemptions); | ||
/* Setters */ | ||
function createCampaign(CampaignParams calldata params, string calldata uri) external returns (uint256 campaignId); | ||
function updateCampaign(uint256 campaignId, CampaignParams calldata params, string calldata uri) external; | ||
function redeem(uint256[] calldata considerationTokenIds, address recipient, bytes calldata extraData) external payable; | ||
} | ||
--- | ||
/* Seaport structs, for reference, used in offer/consideration above */ | ||
enum ItemType { | ||
NATIVE, | ||
ERC20, | ||
ERC721, | ||
ERC1155 | ||
} | ||
struct OfferItem { | ||
ItemType itemType; | ||
address token; | ||
uint256 identifierOrCriteria; | ||
uint256 startAmount; | ||
uint256 endAmount; | ||
} | ||
struct ConsiderationItem extends OfferItem { | ||
address payable recipient; | ||
// (note: psuedocode above, can't currently extend structs in solidity) | ||
} | ||
struct SpentItem { | ||
ItemType itemType; | ||
address token; | ||
uint256 identifier; | ||
uint256 amount; | ||
} | ||
``` | ||
|
||
### Creating campaigns | ||
|
||
When creating a new campaign, `createCampaign` MUST be used and MUST return the newly created `campaignId` along with the `CampaignUpdated` event. The `campaignId` MUST be an incrementing counter starting at `1`. | ||
|
||
### Updating campaigns | ||
|
||
Updates to campaigns MUST use `updateCampaign` and MUST emit the `CampaignUpdated` event. If an address other than the `manager` tries to update the campaign, it MUST revert with `NotManager()`. If the manager wishes to make the campaign immutable, the `manager` MAY be set to the null address. | ||
|
||
### Offer | ||
|
||
If tokens are set in the params `offer`, the tokens MUST implement the `IRedemptionMintable` interface in order to support minting new items. The implementation SHOULD be however the token mechanics are desired. The implementing token MUST return true for ERC-165 `supportsInterface` for the interfaceId of `IRedemptionMintable`, `0x12345678(placeholder, to be set here when finalized)`. | ||
|
||
```solidify | ||
interface IRedemptionMintable { | ||
function mintRedemption(uint256 campaignId, address recipient, ConsiderationItem[] calldata consideration, TraitRedemptions[] calldata traitRedemptions) external; | ||
} | ||
``` | ||
|
||
When `mintRedemption` is called, it is RECOMMENDED to replace the token identifiers in the consideration items and trait redemptions with the items actually being redeemed. | ||
|
||
### Consideration | ||
|
||
Any token may be used in the RedeemableParams `consideration`. This will ensure the token is transferred to the `recipient`. If the token is meant to be burned, the recipient SHOULD be `0x0000000000000000000000000000000000000000`, where the token MUST call its internal burn method to delete the token and reduce totalSupply. If an external token is specified, then the recipient for burning SHOULD be `0x000000000000000000000000000000000000dEaD`, and approval must be given to the token contract as an operator on behalf of the user. | ||
|
||
### Dynamic traits | ||
|
||
Including trait redemptions is optional, but if the token would like to enable trait redemptions the token MUST include E RC-7496 <!-- [E RC-7496] (./e ip-7496.md) --> Dynamic Traits. | ||
|
||
### Signer | ||
|
||
A signer MAY be specified to provide a signature to process the redemption. If the signer is NOT the null address, the signature MUST recover to the signer address via [EIP-712](./eip-712.md) or [ERC-1271](./eip-1271.md). | ||
|
||
The EIP-712 struct for signing MUST be as follows: `SignedRedeem(address owner,uint256[][] tokenIdsPerRedemption,uint256 campaignId,uint256 requirementsIndex, bytes32 redemptionHash, uint256 salt)"` | ||
|
||
### Redeem function and redemption extraData | ||
|
||
The `redeem` function MUST use the `consideration`, `offer`, and `traitRedemptions` specified by the `requirements` determined by the `campaignId` and `requirementsIndex`: | ||
|
||
- Execute the transfers in the `consideration` | ||
- Mutate the traits specified by `traitRedemptions` according to ERC -7496 Dynamic Traits | ||
- Call `mintRedemption()` on every `offer` item | ||
|
||
If any of the supplied tokenIds in `redeem` fail validation or execution, the function MAY execute just the redemptions that were valid and ignore the failed redemptions. | ||
|
||
The `Redemption` event MUST be emitted when any valid redemptions occur. | ||
|
||
The extraData layout MUST conform to the below: | ||
|
||
| bytes | value | description / notes | | ||
| -------- | --------------------------------- | ------------------------------------------------------------------------------------ | | ||
| 0-32 | campaignId | | | ||
| 32-64 | requirementsIndex | index of the campaign requirements met | | ||
| 64-96 | redemptionHash | hash of offchain order ids | | ||
| 96- \* | uint256[] traitRedemptionTokenIds | token ids for trait redemptions, MUST be in same order of campaign TraitRedemption[] | | ||
| \*- (+32) | salt | if signer != address(0) | | ||
| \*- (+\*) | signature | if signer != address(0). can be for EIP-712 or EIP-1271 | | ||
|
||
The `requirementsIndex` MUST be the index in the `requirements` array that satisfies the redemption. This helps reduce gas to find the requirement met. | ||
|
||
The `traitRedemptionTokenIds` specifies the token IDs required for the trait redemptions in the requirements array. The order MUST be the same order of the token addresses expected in the array of `TraitRedemption` structs in the campaign requirement used. | ||
|
||
If the campaign `signer` is the null address the `salt` and `signature` MUST be omitted. | ||
|
||
The `redemptionHash` is designated for offchain redemptions to reference offchain order identifiers to track the redemption to. | ||
|
||
The function MUST check that the campaign is active (using the same boundary check as Seaport, `startTime <= block.timestamp < endTime`). If it is not active, it MUST revert with `NotActive()`. | ||
|
||
### Trait redemptions | ||
|
||
The token MUST respect the TraitRedemption substandards as follows: | ||
|
||
| substandard ID | description | substandard value | | ||
| -------------- | ------------------------------- | ------------------------------------------------------------------ | | ||
| 1 | set value to `traitValue` | prior required value. if blank, cannot be the `traitValue` already | | ||
| 2 | increment trait by `traitValue` | max value | | ||
| 3 | decrement trait by `traitValue` | min value | | ||
|
||
### Max campaign redemptions | ||
|
||
The token MUST check that the `maxCampaignRedemptions` is not exceeded. If the redemption does exceed `maxCampaignRedemptions`, it MUST revert with `MaxCampaignRedemptionsReached(uint256 total, uint256 max)` | ||
|
||
### Metadata URI | ||
|
||
The metadata URI MUST conform to the below JSON schema: | ||
|
||
```json | ||
{ | ||
"$schema": "https://json-schema.org/draft/2020-12/schema", | ||
"type": "object", | ||
"properties": { | ||
"name": { | ||
"type": "string" | ||
}, | ||
"description": { | ||
"type": "string", | ||
"description": "A one-line summary of the redeemable. Markdown is not supported." | ||
}, | ||
"details": { | ||
"type": "string", | ||
"description": "A multi-line or multi-paragraph description of the details of the redeemable. Markdown is supported." | ||
}, | ||
"imageUrls": { | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
}, | ||
"description": "A list of image URLs for the redeemable. The first image will be used as the thumbnail. Will rotate in a carousel if multiple images are provided. Maximum 5 images." | ||
}, | ||
"bannerUrl": { | ||
"type": "string", | ||
"description": "The banner image for the redeemable." | ||
}, | ||
"faq": { | ||
"type": "array", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
"question": { | ||
"type": "string" | ||
}, | ||
"answer": { | ||
"type": "string" | ||
}, | ||
"required": ["question", "answer"] | ||
} | ||
} | ||
}, | ||
"contentLocale": { | ||
"type": "string", | ||
"description": "The language tag for the content provided by this metadata. https://www.rfc-editor.org/rfc/rfc9110.html#name-language-tags" | ||
}, | ||
"maxRedemptionsPerToken": { | ||
"type": "string", | ||
"description": "The maximum number of redemptions per token. When isBurn is true should be 1, else can be a number based on the trait redemptions limit." | ||
}, | ||
"isBurn": { | ||
"type": "string", | ||
"description": "If the redemption burns the token." | ||
}, | ||
"uuid": { | ||
"type": "string", | ||
"description": "An optional unique identifier for the campaign, for backends to identify when draft campaigns are published onchain." | ||
}, | ||
"productLimitForRedemption": { | ||
"type": "number", | ||
"description": "The number of products which are able to be chosen from the products array for a single redemption." | ||
}, | ||
"products": { | ||
"type": "object", | ||
"properties": "https://schema.org/Product", | ||
"required": ["name", "url", "description"] | ||
}, | ||
"traitRedemptions": { | ||
"type": "array", | ||
"items": { | ||
"type": "object", | ||
"properties": { | ||
"substandard": { | ||
"type": "number" | ||
}, | ||
"token": { | ||
"type": "string", | ||
"description": "The token address" | ||
}, | ||
"traitKey": { | ||
"type": "string" | ||
}, | ||
"traitValue": { | ||
"type": "string" | ||
}, | ||
"substandardValue": { | ||
"type": "string" | ||
} | ||
}, | ||
"required": [ | ||
"substandard", | ||
"token", | ||
"traitKey", | ||
"traitValue", | ||
"substandardValue" | ||
] | ||
} | ||
} | ||
}, | ||
"required": ["name", "description", "isBurn"] | ||
} | ||
``` | ||
|
||
Future EIPs MAY inherit this one and add to the above metadata to add more features and functionality. | ||
|
||
### ERC-1155 (Semi-fungibles) | ||
|
||
This standard MAY be applied to ERC-1155 but the redemptions would apply to all token amounts for specific token identifiers. If the ERC-1155 contract only has tokens with amount of 1, then this specification MAY be used as written. | ||
|
||
## Rationale | ||
|
||
The intention of this EIP is to define a consistent standard to enable redeemable entitlements for tokens and onchain traits. This pattern allows for websites to discover, display, and interact with redeemable campaigns. | ||
|
||
## Backwards Compatibility | ||
|
||
As a new EIP, no backwards compatibility issues are present. | ||
|
||
## Test Cases | ||
|
||
Authors have included Foundry tests covering functionality of the specification in the assets folder. | ||
|
||
## Reference Implementation | ||
|
||
Authors have included reference implementations of the specification in the assets folder. | ||
|
||
## Security Considerations | ||
|
||
If trait redemptions are desired, tokens implementing this EIP must properly implement E RC-7496 <!-- [E RC-7496] (./e ip-7496.md) --> Dynamic Traits. | ||
|
||
For tokens to be minted as part of the params `offer`, the `mintRedemption` function contained as part of `IRedemptionMintable` MUST be permissioned and ONLY allowed to be called by specified addresses. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |
Oops, something went wrong.