diff --git a/SIPS/sip-14.md b/SIPS/sip-14.md index 8087312..81742cf 100644 --- a/SIPS/sip-14.md +++ b/SIPS/sip-14.md @@ -2,7 +2,7 @@ sip: 14 title: Redeemable Contract Offerer description: A Seaport Contract Offerer that uses ERC-7496 Dynamic Traits to enable onchain redemptions -author: Ryan Ghods (@ryanio), 0age (@0age) +author: Ryan Ghods (@ryanio), 0age (@0age), Stephan Min (@stephankmin) discussions-to: https://github.com/ProjectOpenSea/SIPs/discussions/18 status: Draft type: Standards Track @@ -13,7 +13,7 @@ requires: 5 ## Abstract -This specification proposes a standard for a Seaport Contract Offerer that uses ERC-7496 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. +This specification proposes a standard for a Seaport Contract Offerer that uses ERC-7496 Dynamic Traits to enable onchain and offchain 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 @@ -23,13 +23,13 @@ Traits and metadata for non-fungible and semi-fungible tokens are often stored o 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. +The contract offerer MUST have the following interface and MUST return `true` for EIP-165 supportsInterface for `0x12345678(placeholder, to be set here when finalized)`, 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); + event Redemption(uint256 indexed campaignId, uint256 requirementsIndex, bytes32 redemptionHash); /* Structs */ struct CampaignParams { @@ -38,8 +38,12 @@ interface IRedeemableContractOfferer { uint32 maxCampaignRedemptions; address manager; // the address that can modify the campaign address signer; // null address means no EIP-712 signature required + CampaignRequirements[] requirements; + } + struct CampaignRequirements { OfferItem[] offer; // items to be minted, can be empty for offchain redeemable - ConsiderationItem[] consideration; // the items you are transferring to recipient + ConsiderationItem[] consideration; // items transferring to recipient + TraitRedemption[] traitRedemptions; // the trait redemptions } struct TraitRedemption { uint8 substandard; @@ -47,7 +51,7 @@ interface IRedeemableContractOfferer { uint256 identifier; bytes32 traitKey; bytes32 traitValue; - bytes substandardValue; + bytes32 substandardValue; } /* Getters */ @@ -69,47 +73,38 @@ Updates to campaigns MUST use `updateCampaign` and MUST emit the `CampaignUpdate ### 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` +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 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); +interface IRedemptionMintable { + function mintRedemption(uint256 campaignId, address recipient, SpentItem[] calldata spent) external; } ``` -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-7496 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`. +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`, since it is against ERC-721 and ERC-1155 specifications to transfer items to the null address. ### 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)"` +The EIP-712 struct for signing MUST be as follows: `SignedRedeem(address owner,address redeemedToken, uint256[] tokenIds,uint256 campaignId,uint256 requirementsIndex, 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 | +| 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 | -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()`. +The contract offerer 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()`. ### Redemptions @@ -133,7 +128,7 @@ The contract offerer MUST check that the `maxCampaignRedemptions` is not exceede ### Metadata URI -The metadata URI MUST follow the following JSON schema: +The metadata URI MUST conform to the below JSON schema: ```json { @@ -178,6 +173,21 @@ The metadata URI MUST follow the following JSON schema: "type": "string", "description": "The language tag for the content provided by this metadata. https://www.rfc-editor.org/rfc/rfc9110.html#name-language-tags" }, + "translations": { + "type": "object", + "properties": { + "locale": { + "type": "string" + }, + "originalContent": { + "type": "string" + }, + "translatedContent": { + "type": "string" + } + }, + "description": "Translations for content provided by this metadata." + }, "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." @@ -188,7 +198,7 @@ The metadata URI MUST follow the following JSON schema: }, "uuid": { "type": "string", - "description": "A unique identifier for the campaign, for backends to identify when draft campaigns are published onchain." + "description": "An optional unique identifier for the campaign, for backends to identify when draft campaigns are published onchain." }, "productLimitForRedemption": { "type": "number", diff --git a/SIPS/sip-15.md b/SIPS/sip-15.md index c639f9d..9ca2c0a 100644 --- a/SIPS/sip-15.md +++ b/SIPS/sip-15.md @@ -53,21 +53,27 @@ The `context` argument MUST be populated based on the "substandards" specified b 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. +todo MUST be compact + 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)` | +| 0 | first consideration item, comparison "equal to", single trait key, zero trait value | `(bytes32 traitKey)` TODO think about this should be in the zone hash, when no extra data is provided | +| 1 | token address and id from first offer item | `(uint8 comparisonEnum, bytes32 traitValue, bytes32 traitKey)` | +| 2 | token address and id from the first consideration item | `(uint8 comparisonEnum, bytes32 traitValue, bytes32 traitKey)` | +| 3 | single token id, single trait key and value(single) | `(uint8 comparisonEnum, address token, uint256 tokenId, bytes32 traitValue, bytes32 traitKey)` | +| 4 | multiple token ids, single trait key and value(multiple) | `(uint8 comparisonEnum, address token, uint256 tokenId, bytes32 traitValue, bytes32 traitKey)[]` | +| 5 | single token id, multiple traitKeys and values | `` | | comparison enum | behavior | | --------------- | ------------------------ | | 0 | equal to | -| 1 | less than | -| 2 | less than or equal to | -| 3 | greater than | -| 4 | greater than or equal to | +| 1 | not equal to | +| 2 | less than | +| 3 | less than or equal to | +| 4 | greater than | +| 5 | greater than or equal to | Additional substandards MAY be specified in subsequent SIPs that inherit this SIP.