Skip to content

Commit

Permalink
Merge pull request #21 from ProjectOpenSea/ryan/update-sips-14-15
Browse files Browse the repository at this point in the history
Updates to SIP-14, SIP-15
  • Loading branch information
0age authored Oct 31, 2023
2 parents a368dc8 + 6a99f23 commit 04e69f7
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 38 deletions.
72 changes: 41 additions & 31 deletions SIPS/sip-14.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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 {
Expand All @@ -38,16 +38,20 @@ 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;
address token;
uint256 identifier;
bytes32 traitKey;
bytes32 traitValue;
bytes substandardValue;
bytes32 substandardValue;
}
/* Getters */
Expand All @@ -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

Expand All @@ -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
{
Expand Down Expand Up @@ -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."
Expand All @@ -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",
Expand Down
20 changes: 13 additions & 7 deletions SIPS/sip-15.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down

0 comments on commit 04e69f7

Please sign in to comment.