-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from ProjectOpenSea/ryan/add-redeemables-sips
Add SIP-14: Redeemable Contract Offerer and SIP-15: Interface for Dynamic Traits Enforcement
- Loading branch information
Showing
2 changed files
with
364 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,268 @@ | ||
--- | ||
sip: 14 | ||
title: Redeemable Contract Offerer | ||
description: A Seaport Contract Offerer that uses ERC-7500 Dynamic Traits to enable onchain redemptions | ||
author: Ryan Ghods (@ryanio), 0age (@0age) | ||
discussions-to: https://github.com/ProjectOpenSea/SIPs/discussions/18 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2023-07-28 | ||
requires: 5 | ||
--- | ||
|
||
## Abstract | ||
|
||
This specification proposes a standard for a Seaport Contract Offerer that uses ERC-7500 Dynamic Traits to enable onchain redemptions. This also allows a Seaport zone to use the Dynamic Traits standard to ensure redemptions cannot be frontrun when NFTs are listed to be sold with a guarantee of certain traits. | ||
|
||
## Motivation | ||
|
||
Traits and metadata for non-fungible and semi-fungible tokens are often stored offchain. This makes it difficult to query and mutate these values in contract code. ERC-7500 Dynamic Traits brings certain traits onchain, to enable contracts like this Redeemable Contract Offerer to use them to create onchain token and offchain product redemptions. | ||
|
||
## 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 contract offerer MUST have the following interface and MUST return `true` for EIP-165 supportsInterface for `0x12345678`, the 4 byte interfaceId of the below. | ||
|
||
```solidity | ||
interface IRedeemableContractOfferer { | ||
/* Events */ | ||
event CampaignUpdated(uint256 indexed campaignId, CampaignParams params, string URI); | ||
event Redemption(uint256 indexed campaignId, bytes32 redemptionHash); | ||
/* 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 | ||
OfferItem[] offer; // items to be minted, can be empty for offchain redeemable | ||
ConsiderationItem[] consideration; // the items you are transferring to recipient | ||
} | ||
struct TraitRedemption { | ||
uint8 substandard; | ||
address token; | ||
uint256 identifier; | ||
bytes32 traitKey; | ||
bytes32 traitValue; | ||
bytes 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; | ||
} | ||
``` | ||
|
||
### 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 EIP-165 `supportsInterface` for the interfaceIds of: `IERC721RedemptionMintable: 0x12345678` or `IERC1155RedemptionMintable: 0x12345678` | ||
|
||
```solidify | ||
interface IERC721RedemptionMintable { | ||
function mintRedemption(address to, SpentItem[] calldata spent) external returns (uint256[] memory tokenIds); | ||
} | ||
interface IERC1155RedemptionMintable { | ||
function mintRedemption(address to, SpentItem[] calldata spent) external returns (uint256[] memory tokenIds, uint256[] amounts); | ||
} | ||
``` | ||
|
||
The array length return values of `tokenIds` and `amounts` for `IERC1155RedemptionMintable` MUST equal each other. | ||
|
||
### 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 `0x000000000000000000000000000000000000dEaD`. | ||
|
||
### Dynamic traits | ||
|
||
The contract offerer MUST include the ERC-7500 Dynamic Traits interface itself in case the campaign is a trait redemption and the specified token does not implement Dynamic Traits itself. According to the Dynamic Traits specification for "registry" functionality, the first 20 bytes of the `traitKey` should be the contract address to associate the proper `tokenId`. | ||
|
||
### 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 or EIP-1271. | ||
|
||
The EIP-712 struct for signing MUST be as follows: `SignedRedeem(address owner,address redeemedToken, uint256[] tokenIds,bytes32 redemptionHash, uint256 salt)"` | ||
|
||
### AdvancedOrder extraData | ||
|
||
When interacting with the contract offerer via Seaport, the extraData/context layout for the AdvancedOrder MUST follow: | ||
|
||
| bytes | value | description / notes | | ||
| -------- | ----------------- | ---------------------------------------------------------------- | | ||
| 0-32 | campaignId | | | ||
| 32-64 | redemptionHash | hash of offchain order ids | | ||
| 64-\* | TraitRedemption[] | see TraitRedemption struct. empty array for no trait redemptions | | ||
| \*-(+32) | salt | if signer != address(0) | | ||
| \*-(+\*) | signature | if signer != address(0). can be for EIP-712 or EIP-1271 | | ||
|
||
Upon redemption, the contract offerer MUST check that the campaign is still active (using the same boundary check as Seaport, `startTime <= block.timestamp < endTime`). If it is, it MUST revert with `NotActive()`. | ||
|
||
### Redemptions | ||
|
||
The `Redemption` event MUST be emitted when any valid redemptions occur. The `Redemption` event is simplified to deduplicate information that is present in the `OrderFulfilled` event from Seaport. | ||
|
||
### Trait redemptions | ||
|
||
The contract offerer 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 | | ||
|
||
Additional substandards MAY be specified in subsequent SIPs that inherit this SIP. | ||
|
||
### Max campaign redemptions | ||
|
||
The contract offerer 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 follow the following 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": "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": "A 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 SIPs 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 SIP is to define a consistent standard to enable redeemable entitlements for tokens and onchain traits. This contract offerer pattern allows for websites to discover, display, and interact with redeemable campaigns. | ||
|
||
## Backwards Compatibility | ||
|
||
As a new SIP, no backwards compatibility issues are present. | ||
|
||
## Test Cases | ||
|
||
Test cases can be found in [https://github.com/ProjectOpenSea/redeemables/tree/main/test](https://github.com/ProjectOpenSea/redeemables/tree/main/test) | ||
|
||
## Reference Implementation | ||
|
||
The reference implementation for the Redeemable Contract Offerer can be found at [https://github.com/ProjectOpenSea/redeemables/blob/main/src/RedeemableContractOfferer.sol](https://github.com/ProjectOpenSea/redeemables/blob/main/src/RedeemableContractOfferer.sol) | ||
|
||
## Security Considerations | ||
|
||
Tokens must properly implement EIP-7500 Dynamic Traits and allow the Redeemable Contract Offerer to use the `setTrait` method to enforce the full security of the standard. | ||
|
||
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 redeemable contract offerers specified by address. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |
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,96 @@ | ||
--- | ||
sip: 15 | ||
title: Interface for Dynamic Traits Enforcement | ||
description: A Seaport interface for specifying and enforcing values of ERC-7500 Dynamic Traits. | ||
author: Ryan Ghods (@ryanio), James Wenzel (emo.eth) | ||
discussions-to: https://github.com/ProjectOpenSea/SIPs/discussions/19 | ||
status: Draft | ||
type: Standards | ||
category (*only required for Standards Track): Interface | ||
created: 2023-08-14 | ||
requires (*optional): 5, 6 | ||
--- | ||
|
||
## Abstract | ||
|
||
This SIP defines a standard interface for Seaport zones and contract offerers to specify and enforce ERC-7500 Dynamic Traits during order fulfillment. | ||
|
||
## Motivation | ||
|
||
Token metadata is typically specified at offchain locations but with ERC-7500 Dynamic Traits certain metadata values can be queried onchain. This can help protect NFTs against frontrunning during purchase where the value of traits changes the expected value of the NFT. With this SIP, zones and contract offerers can enforce the value of certain traits at time of order fulfillment. | ||
|
||
## 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. | ||
|
||
### Requirements | ||
|
||
This SIP requires a single variable data array as part of supplied `extraData`. | ||
|
||
Zones or contract offerers that do not implement additional SIPs must support extraData version byte `0x00` in accordance with SIP-6, while zones or contract offerers that implement additional SIPs with their own data requirements will require other version bytes. | ||
|
||
Zones or contract offerers implementing this SIP MUST return a schema with an `id` of X as part of the `schemas` array returned by `getSeaportMetadata()` in accordance with SIP-5. They also MUST return an associated `metadata` parameter on the returned schema, decoded as `(bytes32 domainSeparator, string memory apiEndpoint, uint256[] memory substandards, string memory documentationURI)`. | ||
|
||
### Metadata Verification | ||
|
||
The data for verifying a protected order is sent as part of the order's `extraData` and must contain at least XX bytes. The `extraData` MUST be formatted according to [SIP-6](./sip-6.md) based on the other SIPs returned in accordance with SIP-5. | ||
|
||
| field | bytes | | ||
| ---------------------------------------------------- | ----- | | ||
| variable context data (format based on substandards) | 0-end | | ||
|
||
If the order does not conform to the expected metadata values, the zone or contract offerer MUST revert with `error InvalidDynamicTraitValue(address token, bytes32 traitKey, bytes32 expectedTraitValue, bytes32 actualTraitValue);` | ||
|
||
### Interface | ||
|
||
If the implementing contract is a zone, it MUST provide a `validateOrder()` function that adheres to the Seaport zone interface to decode the extra data component. | ||
|
||
If the implementing contract is a contract offerer, it MUST provide `previewOrder()`, `generateOrder()`, and `ratifyOrder()` functions that adheres to the Seaport contract offerer interface that decodes the extra data. | ||
|
||
### Substandards | ||
|
||
The `context` argument MUST be populated based on the "substandards" specified by the zone or contract offerer; these substandards will be encoded in accordance with SIP-6 versioning with the assumption that all necessary data is to be treated as "variable" data arrays. | ||
|
||
The `context` MUST start with a byte identifying the substandard ID below. The byte SHOULD be 1-indexed, but for gas efficiency reasons, 00 MAY also be used as an alias to reference substandard ID 1. | ||
|
||
Initial substandards include: | ||
|
||
| substandard ID | description | decoding scheme | | ||
| -------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | | ||
| 1 | required traitValue for traitKey (single) | `(uint8 comparisonEnum, address token, bytes32 traitValue, bytes32 traitKey)` | | ||
| 2 | required traitValues for traitKeys (multiple) | `(uint8 comparisonEnum, address token, bytes32 traitValue, bytes32 traitKey)[]` | | ||
| 3 | required hash of values of all traitKeys (up to traitKeysLength, use uint256 max for all possible traitKeys) | `(address token, uint256 traitKeysLength, bytes32 expectedHash)` | | ||
|
||
| comparison enum | behavior | | ||
| --------------- | ------------------------ | | ||
| 0 | equal to | | ||
| 1 | less than | | ||
| 2 | less than or equal to | | ||
| 3 | greater than | | ||
| 4 | greater than or equal to | | ||
|
||
Additional substandards MAY be specified in subsequent SIPs that inherit this SIP. | ||
|
||
## Rationale | ||
|
||
This specification was developed to ensure a consistent experience to protect orders against frontrunning valuable traits. | ||
|
||
## Backwards Compatibility | ||
|
||
As a newly proposed standard there is no issue with backwards compatibility. | ||
|
||
## Test Cases | ||
|
||
Test cases are located in the [Dynamic Traits repository](https://github.com/ProjectOpenSea/dynamic-traits/blob/main/src/lib/DynamicTraits.sol). | ||
|
||
## Reference Implementation | ||
|
||
The reference implementation can be found in the [Dynamic Traits repository](https://github.com/ProjectOpenSea/dynamic-traits/blob/main/test/ERC721DynamicTraits.t.sol). | ||
|
||
## Security Considerations | ||
|
||
- For the zone to work as intended tokens must properly implement ERC-7500 Dynamic Traits. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |