Rarible's protocol includes contracts, standards, and APIs for:
Minting
mintAndTransfer
call is made on chain when the order is filledExchange (Buy, Sell, Bid)
approve
the Rarible exchange to transfer on their behalfIndexer
https://api-reference.rarible.com/
Base URL | Network | Chain ID |
---|---|---|
https://api.rarible.com/protocol/v0.1/ | Mainnet | 1 |
https://api-staging.rarible.com/protocol/v0.1/ | Rinkeby | 4 |
https://api-dev.rarible.com/protocol/v0.1/ | Ropsten | 3 |
The Rarible starter app is forked from Scaffold-Eth
https://github.com/ipatka/scaffold-eth/tree/rarible-starter-app
Contract | Rinkeby Address | Ropsten Address |
---|---|---|
Asset Contract ERC721 | 0x6ede7f3c26975aad32a475e1021d8f6f39c89d82 | 0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05 |
Asset Contract ERC1155 | 0x1AF7A7555263F275433c6Bb0b8FdCD231F89B1D7 | 0x6a94aC200342AC823F909F142a65232E2f052183 |
Exchange Contract | 0x1e1B6E13F0eB4C570628589e3c088BC92aD4dB45 | |
NFT Transfer Proxy (for NFT approvals) | 0x7d47126a2600E22eab9eD6CF0e515678727779A6 | 0xf8e4ecac18b65fd04569ff1f0d561f74effaa206 |
ERC20 Transfer Proxy (for ERC20 approvals) | 0x2FCE8435F0455eDc702199741411dbcD1B7606cA |
Minting through Rarible Protocol contracts are now built around the new Lazy Mint feature.
Lazy Minting: Lazy minting allows the item to be created and stored off-chain until someone purchases/transfers this item, only at this point does the item get minted on-chain.
When minting through the protocol's asset contracts listed above, developers should call mintAndTransfer()
for NFT creation.
Direct calls to mint()
should be avoided and replaced with mintAndTransfer()
. However, standard minting is still possible with this function. See ERC721 Standard Minting or ERC1155 Standard Minting for details.
mintAndTransfer(LibERC721LazyMint.Mint721Data memory data, address to)
Mint721Data Parameter Structure
struct Mint721Data {
uint tokenId;
string uri;
LibPart.Part[] creators;
LibPart.Part[] royalties;
bytes[] signatures;
}
Parameters
The tokenId
must be supplied as a uint256, which is a unique identifying number for the token.
The tokenId
is made up of two sections, the first 20 bytes is the user's address and the next 12 bytes can be any random number. This API will allow you to get the next available ID.
This is the suffix for the tokenURI. The prefix for Rarible protocol contracts is ipfs://
/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp
ipfs://ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp
Note: If you are not storing your metadata on IPFS, you will need to create your own custom collection contract instead of using the protocol's asset contracts. See Implementation for details.
creators
is an array of addresses and values. The LibPart.Part
struct it derives from is provided below.
struct Part {
address payable account;
uint96 value;
}
This array should contain all the addresses of the creators of this token with their respective ownership or contribution to the creation - in basis points. The address array is public and can be queried by anyone.
Sum of the fields value in this array should be 10000 (100% in basis points). Can be divided in any number of ways.
I.e. The following array, [[0x12345..., 5000], [0x6789..., 5000]]
, associates the creation of the given NFT to 2 creators at an equal 50% distribution.
royalties
is an array of addresses and values. Like creators
, it's also derived from the LibPart.Part
struct provided below.
struct Part {
address payable account;
uint96 value;
}
The fees array is public and can be queried by anyone. Values are specified in basis points. For example, 2000 means 20%.
I.e. One address recieves 20% royalties with the following array, [[0x12345..., 2000]]
. But more than one address can be provided to recieve royalties at specified percentages.
signatures
is an array of wallet signatures for this transaction from every creator.
However, an empty signature, [[0x]]
, can be passed if the creator is minting immediately instead of creating a Lazy Mint. The steps for standard minting are provided in ERC721 Standard Minting.
ERC721 mintAndTransfer ABI
// ABI for ERC-721 mintAndTransfer
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "string",
"name": "uri",
"type": "string"
},
{
"components": [
{
"internalType": "address payable",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"internalType": "struct LibPart.Part[]",
"name": "creators",
"type": "tuple[]"
},
{
"components": [
{
"internalType": "address payable",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"internalType": "struct LibPart.Part[]",
"name": "royalties",
"type": "tuple[]"
},
{
"internalType": "bytes[]",
"name": "signatures",
"type": "bytes[]"
}
],
"internalType": "struct LibERC721LazyMint.Mint721Data",
"name": "data",
"type": "tuple"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "mintAndTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
If you're storing your metadata on IPFS, you can mint through the Rarible Protocol asset contracts without conflict.
Lazy minting requires the creator's signature in order to allow minting of their NFT when someone else purchases it and to retain its provenance.
We'll go through the steps of getting the creator's signature and creating a lazy mint below. You can also find various code implementations for lazy minting here, here, and here.
Step 1: Generate a token ID.
GET from
https://api-dev.rarible.com/protocol/v0.1/ethereum/nft/collections/{ContractAddress}/generate_token_id?minter=${account}
// Sample Call
const res = await fetch('https://api-dev.rarible.com/protocol/v0.1/ethereum/nft/collections/{ContractAddress}/generate_token_id?minter=${creator}').then((res) => res.json());
Response
{
tokenId: "10269532675691974816893214588076010230265315839066808147818573374451427049545",
signature: {
r: "0x9f73810b77cee6ce00f3924091b22030ba707823bde53635d5ef2a3a1a605e8e"
s: "0x00803ec40e179172973347ba9f1a8c8dd094e5b2e2e19504a09017f4358f2b1e"
v: 28
}
}
Get the tokenId
from the response object.
Step 2: Create the Lazy Mint Request Body to be signed by the creator
{
"@type": "ERC721",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"uri": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp",
"creators": [
{
account: "0x1234...",
value: "10000"
}
],
"royalties": [
{
account: "0x1234...",
value: 2000
}
],
};
Step 3: Creator signs the provided typed data, thereby granting permission to mint their NFT upon purchase.
Note See Signatures for more details on typed data and EIP-712 and EIP-1271.
First, construct the typed data structure:
"types": {
"EIP712Domain" [
{
type: "string",
name: "name",
},
{
type: "string",
name: "version",
},
{
type: "uint256",
name: "chainId",
},
{
type: "address",
name: "verifyingContract",
}
],
"Mint721": [
{ name: "tokenId", type: "uint256" },
{ name: "tokenURI", type: "string" },
{ name: "creators", type: "Part[]" },
{ name: "royalties", type: "Part[]" }
],
"Part": [
{ name: "account", type: "address" },
{ name: "value", type: "uint96" }
]
},
"domain": {
name: "Mint721",
version: "1",
chainId: 3,
verifyingContract: "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05"
},
"primaryType": "Mint721",
"message": {
"@type": "ERC721",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"tokenURI": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp"
"uri": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp",
"creators": [
{
account: "0x1234...",
value: "10000"
}
],
"royalties": [
{
account: "0x1234...",
value: 2000
}
],
};
Then provide the data structure above to the creator for signing.
// Sample code
async function signTypedData(web3Provider, from, dataStructure) {
const msgData = JSON.stringify(dataStructure);
const signature = await web3Provider.send("eth_signTypedData_v4", [from, msgData]);
const sig0 = sig.substring(2);
const r = "0x" + sig0.substring(0, 64);
const s = "0x" + sig0.substring(64, 128);
const v = parseInt(sig0.substring(128, 130), 16);
return {
dataStructure,
signature,
v,
r,
s,
};
}
Finally, get the signature
from the object that the function above returns and add it as the final field of the Lazy Mint Request Body you created in Step 2.
E.g.
{
"@type": "ERC721",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"uri": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp",
"creators": [
{
account: "0x1234...",
value: "10000"
}
],
"royalties": [
{
account: "0x1234...",
value: 2000
}
],
"signatures": ["0x2f1e8dd2838930f0230a9fcbb2977779838eb8dd44391af1…a6cb767fa157d5fb54c084e8ffb41be1c1bc5b6f067fd681b"]
};
Step 4: Create your Lazy Minted NFT
POST to https://api-dev.rarible.com/protocol/v0.1/ethereum/nft/mints
{
"@type": "ERC721",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"uri": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp",
"creators": [
{
account: "0x1234...",
value: "10000"
}
],
"royalties": [
{
account: "0x1234...",
value: 2000
}
],
"signatures": ["0x2f1e8dd2838930f0230a9fcbb2977779838eb8dd44391af1…a6cb767fa157d5fb54c084e8ffb41be1c1bc5b6f067fd681b"]
};
Response
{
"id": ""0x3437df037bbbeb1aa3e417b32154bc2bb5da1c04:10269532675691974816893214588076010230265315839066808147818573374451427049549"",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"creators": [
{
"account": "0x1234...",
"value": 10000
}
]
"supply": 1,
"lazySupply": 1,
"owners": [
"0x1234..."
],
"royalties": [
{
"account": "0x1234...",
"value": 2000
}
],
"pending": [
{
"date": "2019-08-24T14:15:22Z",
"owner": "0x1234...",
"from": "0x1234...",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"value": 0,
"type": "TRANSFER"
}
]
}
You've successfully created a Lazy Minted NFT with the Rarible Protocol! 🎉
You can mint NFTs through the Rarible asset contracts using mintAndTransfer
like you would for a standard mint
call. You just need to provide all the expected parameters seen below.
mintAndTransfer(LibERC721LazyMint.Mint721Data memory data, address to)
struct Mint721Data {
uint tokenId;
string uri;
LibPart.Part[] creators;
LibPart.Part[] royalties;
bytes[] signatures;
}
You can do so by instantiating the contract in your app and calling the function directly using ethers.js
or web3.js
.
Note For the signature, since you are minting an NFT as a direct call and not a lazy mint, you simply pass an empty signature. E.g. 0x
.
Note Royalties are set as basis point, so 1000 = 10%. More info
async function mintNow() {
// Get a token id
const tokenId = await fetch(`https://api-dev.rarible.com/protocol/v0.1/ethereum/nft/collections/${contractAddress}/generate_token_id?minter=${account}`);
// Instantiate the contract
const provider = new ethers.providers.Web3Provider(userWalletProvider);
const signer = provider.getSigner();
const contract = new ethers.Contract(contractAddress, abi, signer);
// Call the function
const tx = await contract.mintAndTransfer(
[
tokenId.tokenId,
uri,
[[creator, 5000], [creator2, 5000]], // You can assign one or add multiple creators, but the value must total 10000
[[creator, 1000], [creator2, 1000]], // Royalties are set as basis point, so 1000 = 10%.
["0x"]
],
minter,
);
const receipt = await tx.wait();
console.log('Minting Success', receipt);
}
mintAndTransfer(LibERC1155LazyMint.Mint1155Data memory data, address to, uint256 _amount)
Mint1155Data Parameter Structure
struct Mint1155Data {
uint tokenId;
string uri;
uint supply;
LibPart.Part[] creators;
LibPart.Part[] royalties;
bytes[] signatures;
}
Parameters
The tokenId
must be supplied as a uint256, which is a unique identifying number for the token.
The tokenId
is made up of two sections, the first 20 bytes is the user's address and the next 12 bytes can be any random number. This API will allow you to get the next available ID.
This is the suffix for the tokenURI. The prefix for Rarible protocol contracts is ipfs://
/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp
ipfs://ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp
Note: If you are not storing your metadata on IPFS, you will need to create your own custom collection contract instead of using the protocol's asset contracts. See Implementation for details.
supply
should be a uint256, this is the number of copies (or Editions) of this token that will ever exist. (Maximum value is 2**256 - 1).
creators
is an array of addresses and values. The LibPart.Part
struct it derives from is provided below.
struct Part {
address payable account;
uint96 value;
}
This array should contain all the addresses of the creators of this token with their respective ownership or contribution to the creation - in basis points. The address array is public and can be queried by anyone.
Sum of the fields value in this array should be 10000 (100% in basis points). Can be divided in any number of ways.
I.e. The following array, [[0x12345..., 5000], [0x6789..., 5000]]
, associates the creation of the given NFT to 2 creators at an equal 50% distribution.
royalties
is an array of addresses and values. Like creators
, it's also derived from the LibPart.Part
struct provided below.
struct Part {
address payable account;
uint96 value;
}
The fees array is public and can be queried by anyone. Values are specified in basis points. For example, 2000 means 20%.
I.e. One address recieves 20% royalties with the following array, [[0x12345..., 2000]]
. But more than one address can be provided to recieve royalties at specified percentages.
signatures
is an array of wallet signatures for this transaction from every creator.
However, an empty signature, [[0x]]
, can be passed if the creator is minting immediately instead of creating a Lazy Mint. The steps for standard minting are provided in ERC1155 Standard Minting.
ERC1155 mintAndTransfer ABI
// ABI for ERC-1155 mintAndTransfer
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "string",
"name": "uri",
"type": "string"
},
{
"internalType": "uint256",
"name": "supply",
"type": "uint256"
},
{
"components": [
{
"internalType": "address payable",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"internalType": "struct LibPart.Part[]",
"name": "creators",
"type": "tuple[]"
},
{
"components": [
{
"internalType": "address payable",
"name": "account",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"internalType": "struct LibPart.Part[]",
"name": "royalties",
"type": "tuple[]"
},
{
"internalType": "bytes[]",
"name": "signatures",
"type": "bytes[]"
}
],
"internalType": "struct LibERC1155LazyMint.Mint1155Data",
"name": "data",
"type": "tuple"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "mintAndTransfer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
If you're storing your metadata on IPFS, you can mint through the Rarible Protocol asset contracts without conflict.
Lazy minting requires the creator's signature in order to allow minting of their NFT when someone else purchases it and to retain its provenance.
We'll go through the steps of getting the creator's signature and creating a lazy mint below. You can also find various code implementations for lazy minting here, here, and here.
Step 1: Generate a token ID.
GET from
https://api-staging.rarible.com/protocol/v0.1/ethereum/nft/collections/{ContractAddress}/ generate_token_id?minter=${account}
// Sample Call
const res = await fetch('https://api-dev.rarible.com/protocol/v0.1/ethereum/nft/collections/{ContractAddress}/generate_token_id?minter={creator}').then((res) => res.json());
Response
{
tokenId: "10269532675691974816893214588076010230265315839066808147818573374451427049545",
signature: {
r: "0x9f73810b77cee6ce00f3924091b22030ba707823bde53635d5ef2a3a1a605e8e"
s: "0x00803ec40e179172973347ba9f1a8c8dd094e5b2e2e19504a09017f4358f2b1e"
v: 28
}
}
Get the tokenId
from the response object.
Step 2: Create the Lazy Mint Request Body to be signed by the creator
{
"@type": "ERC1155",
"contract": "0x6a94aC200342AC823F909F142a65232E2f052183 ",
"tokenId": tokenId,
"uri": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp",
"supply": 10,
"creators": [
{
account: "0x1234...",
value: "10000"
}
],
"royalties": [
{
account: "0x1234...",
value: 2000
}
],
};
Step 3: Creator signs the provided typed data, thereby granting permission to mint their NFT upon purchase.
Note See Signatures for more details on typed data and EIP-712 and EIP-1271.
First, construct the typed data structure:
"types": {
"EIP712Domain" [
{
type: "string",
name: "name",
},
{
type: "string",
name: "version",
},
{
type: "uint256",
name: "chainId",
},
{
type: "address",
name: "verifyingContract",
}
],
"Mint1155": [
{ name: "tokenId", type: "uint256" },
{ name: 'supply', type: 'uint256' },
{ name: "tokenURI", type: "string" },
{ name: "creators", type: "Part[]" },
{ name: "royalties", type: "Part[]" }
],
"Part": [
{ name: "account", type: "address" },
{ name: "value", type: "uint96" }
]
},
"domain": {
name: "Mint1155",
version: "1",
chainId: 3,
verifyingContract: "0x6a94aC200342AC823F909F142a65232E2f052183 "
},
"primaryType": "Mint1155",
"message": {
"@type": "ERC1155",
"contract": "0x6a94aC200342AC823F909F142a65232E2f052183 ",
"tokenId": tokenId,
"tokenURI": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp"
"uri": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp",
"supply": 10,
"creators": [
{
account: "0x1234...",
value: "10000"
}
],
"royalties": [
{
account: "0x1234...",
value: 2000
}
],
};
Then provide the data structure above to the creator for signing.
// Sample code
async function signTypedData(web3Provider, from, dataStructure) {
const msgData = JSON.stringify(dataStructure);
const signature = await web3Provider.send("eth_signTypedData_v4", [from, msgData]);
const sig0 = sig.substring(2);
const r = "0x" + sig0.substring(0, 64);
const s = "0x" + sig0.substring(64, 128);
const v = parseInt(sig0.substring(128, 130), 16);
return {
dataStructure,
signature,
v,
r,
s,
};
}
Finally, get the signature
from the object that the function above returns and add it as the final field of the Lazy Mint Request Body you created in Step 2.
E.g.
{
"@type": "ERC1155",
"contract": "0x6a94aC200342AC823F909F142a65232E2f052183 ",
"tokenId": tokenId,
"uri": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp",
"supply": 10,
"creators": [
{
account: "0x1234...",
value: "10000"
}
],
"royalties": [
{
account: "0x1234...",
value: 2000
}
],
"signatures": ["0x2f1e8dd2838930f0230a9fcbb2977779838eb8dd44391af1…a6cb767fa157d5fb54c084e8ffb41be1c1bc5b6f067fd681b"]
};
Step 4: Create your Lazy Minted NFT
POST to https://api-dev.rarible.com/protocol/v0.1/ethereum/nft/mints
{
"@type": "ERC721",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"uri": "/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp",
"supply": 10
"creators": [
{
account: "0x1234...",
value: "10000"
}
],
"royalties": [
{
account: "0x1234...",
value: 2000
}
],
"signatures": ["0x2f1e8dd2838930f0230a9fcbb2977779838eb8dd44391af1…a6cb767fa157d5fb54c084e8ffb41be1c1bc5b6f067fd681b"]
};
Response
{
"id": ""0x3437df037bbbeb1aa3e417b32154bc2bb5da1c04:10269532675691974816893214588076010230265315839066808147818573374451427049549"",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"creators": [
{
"account": "0x1234...",
"value": 10000
}
]
"supply": 1,
"lazySupply": 1,
"owners": [
"0x1234..."
],
"royalties": [
{
"account": "0x1234...",
"value": 2000
}
],
"pending": [
{
"date": "2019-08-24T14:15:22Z",
"owner": "0x1234...",
"from": "0x1234...",
"contract": "0xB0EA149212Eb707a1E5FC1D2d3fD318a8d94cf05",
"tokenId": tokenId,
"value": 0,
"type": "TRANSFER"
}
]
}
You've successfully created a Lazy Minted NFT with the Rarible Protocol! 🎉
You can mint NFTs through the Rarible asset contracts using mintAndTransfer
like you would for a standard mint
call. You just need to provide all the expected parameters seen below.
mintAndTransfer(LibERC1155LazyMint.Mint1155Data memory data, address to, uint256 _amount)
struct Mint1155Data {
uint tokenId;
string uri;
uint supply;
LibPart.Part[] creators;
LibPart.Part[] royalties;
bytes[] signatures;
}
You can do so by instantiating the contract in your app and calling the function directly using ethers.js
or web3.js
.
Note For the signature, since you are minting an NFT as a direct call and not a lazy mint, you simply pass an empty signature. E.g. 0x
.
Note Royalties are set as basis point, so 1000 = 10%. More info
async function mintNow() {
// Get a token id
const tokenId = await fetch(`https://api-dev.rarible.com/protocol/v0.1/ethereum/nft/collections/${contractAddress}/generate_token_id?minter=${account}`);
// Instantiate the contract
const provider = new ethers.providers.Web3Provider(userWalletProvider);
const signer = provider.getSigner();
const contract = new ethers.Contract(contractAddress, abi, signer);
// Call the function
const tx = await contract.mintAndTransfer(
[
tokenId.tokenId,
uri,
totalSupply,
[[creator, 5000], [creator2, 5000]], // You can assign one or add multiple creators, but the value must total 10000
[[creator, 1000], [creator2, 1000]], // Royalties are set as basis point, so 1000 = 10%.
["0x"]
],
minter,
amount
);
const receipt = await tx.wait();
console.log('Minting Success', receipt);
}
If you are not storing your metadata on IPFS, you will need to use your own contracts that have a baseURI
better suited for where the metadata is stored.
As a starting point, so long as your contract follows the ERC-721 or ERC-1155 standard it's NFTs can be bought and sold on Rarible and most other NFT marketplaces. Helpful guides on these standards can be found at OpenZeppelin.
However, you'll need to add more to support royalties and lazy minting as these are not built into the current standards. To support royalties, your contract will need to inherit from the appropriate interfaces, which you can find here or here for an upgradeable version.
Similarly for lazy minting support, you will need to add a mintAndTransfer
function in your contract for the protocol to call that inherits the expected behavior. You can add this yourself or use this interface.
In many cases, it may be easier and faster to just fork the protocol contracts you wanted to use and change the baseURI
and any other data upon deployment.
Note You can supply your own tokenId
instead of getting one from the API call used for Lazy Minting when rolling your own contracts, however, the token id needs to have the minter's address followed by 96 bits, which can include any number you want, in order to pass the require
checks in the default mintAndTransfer
function. Alternatively, you can still use the generate token id API used above to supply a tokenId for them.
Rarible defines an interface to query royalties from a contract. This is implemented on the standard Rarible token contracts.
It exposes a method called getRoyalties
which expects an ID as input (usually tokenId) and returns an array of accounts & basis points.
function getRoyalties(uint256 id) override external view returns (LibPart.Part[] memory) {
return royalties[id];
}
The exchange contract interacts with the Rarible royalties implementation indirectly through a Royalty Registry. The registry checks if the NFT contract supports the expected interface, and if so queries for the Rarible royalties array.
This allows for Rarible to support different royalty standards for different collections.
The general process for completing an order with the Rarible Exchange is as follows:
TODO swimlanes
Each order (both buy & sell) consist of a makeAsset
and a takeAsset
Make Asset is what you are sending.
Take Asset is what you are accepting in return
Field | Description | Required |
---|---|---|
Maker | Address of entity giving up makeAsset |
Yes |
makeAsset | Asset the entity is giving up | Yes |
taker | Address of counterparty | no, if 0 then anyone can fill the order |
takeAsset | Asset the entity is receving | Yes |
salt | nonce for signatures submitted with the order | Generally signatures are only needed if msg.sender != maker |
start | uint - order can't be filled before this time | no |
end | uint - order can't be filled after this time | no |
dataType | bytes4, usually hash of a string like v1 or v2 | Yes |
data | generic bytes . Can be used for protocol extensions |
no |
Field | Description | Required |
---|---|---|
assetType | Specifies ETH, specific ERC20, ERC721, ERC1155 | Yes |
value | uint | Yes |
Field | Description | Required |
---|---|---|
assetClass | bytes4 specifies ETH, ERC20, ERC721 | Yes |
data | bytes - generic data depending on tp. Ex. address for ERC20, token + tokenID for ERC721 | yes |
Asset Class data field is calculated as follows
bytes4 constant public ETH_ASSET_CLASS = bytes4(keccak256("ETH"));
bytes4 constant public ERC20_ASSET_CLASS = bytes4(keccak256("ERC20"));
bytes4 constant public ERC721_ASSET_CLASS = bytes4(keccak256("ERC721"));
bytes4 constant public ERC1155_ASSET_CLASS = bytes4(keccak256("ERC1155"));
All asset types get encoded using these helper functions
https://github.com/rariblecom/protocol-contracts/blob/master/exchange-v2/test/assets.js
assetClass
: Truncated hash of string "ERC721"
data
: ABI encoded parameters of address
and tokenId
value
: 1
ERC721 Input (Pre encoding)
{
assetType: {
assetClass: "ERC721",
token: "0x25646b08d9796ceda5fb8ce0105a51820740c049",
tokenId: "0xc66d094ed928f7840a6b0d373c1cd825c97e3c7c00000000000000000000000a"
},
value: "1",
}
assetClass
: Truncated hash of string "ERC1155"
data
: ABI encoded parameters of address
and tokenId
value
: 1->totalSupply
ERC1155 Input (Pre encoding)
{
assetType: {
assetClass: "ERC1155",
token: "0x25646b08d9796ceda5fb8ce0105a51820740c049",
tokenId: "0xc66d094ed928f7840a6b0d373c1cd825c97e3c7c00000000000000000000000a"
},
value: "100",
}
assetClass
: Truncated hash of string "ERC20"
data
: ABI encoded parameters of address
value
: 1->totalSupply
ERC20 Input (Pre encoding)
{
assetType: {
assetClass: "ERC20",
token: "0x25646b08d9796ceda5fb8ce0105a51820740c049",
},
value: "10000000000000000",
}
assetClass
: Truncated hash of string "ETH"
data
: 0x
value
: 1->1e18
Input (pre encoding)
{
assetType: {
assetClass: "ETH"
},
value: "10000000000000000",
},
Any asset can be added
assetClass
: Truncated hash of any string
data
: Whatever is relevant
value
: some uint
You can only fill an ETH order if your side of the order is providing the ETH. Otherwise you would have to use WETH which has the transferFrom capability.
First Encode the order for signing
POST to https://api-staging.rarible.com/protocol/v0.1/ethereum/order/encoder/order
{
"type": "RARIBLE_V2",
"maker": "0x744222844bFeCC77156297a6427B5876A6769e19",
"make": {
"assetType": {
"assetClass": "ERC721",
"contract": "0xcfa14f6DC737b8f9e0fC39f05Bf3d903aC5D4575",
"tokenId": 1
},
"value": "1"
},
"take": {
"assetType": {
"assetClass": "ETH"
},
"value": "1000000000000000000"
},
"data": {
"dataType": "RARIBLE_V2_DATA_V1",
"payouts": [],
"originFees": []
},
"salt": "3621"
}
Response:
{
"maker": "0x744222844bfecc77156297a6427b5876a6769e19",
"makeAsset": {
"assetType": {
"assetClass": "0x73ad2146",
"data": "0x000000000000000000000000cfa14f6dc737b8f9e0fc39f05bf3d903ac5d45750000000000000000000000000000000000000000000000000000000000000001"
},
"value": "1"
},
"taker": "0x0000000000000000000000000000000000000000",
"takeAsset": {
"assetType": {
"assetClass": "0xaaaebeba",
"data": "0x"
},
"value": "1000000000000000000"
},
"salt": "3621",
"start": "0",
"end": "0",
"dataType": "0x4c234266",
"data": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
Then Sign the order
async function sign(provider, order, account, verifyingContract) {
const chainId = Number(provider._network.chainId);
const data = EIP712.createTypeData({
name: "Exchange",
version: "2",
chainId,
verifyingContract
}, 'Order', order, Types);
console.log({data})
return (await EIP712.signTypedData(provider, account, data)).sig;
}
Then Post the order to Rarible indexer
POST to https://api-staging.rarible.com/protocol/v0.1/ethereum/order/orders
Payload
{
"type": "RARIBLE_V2",
"maker": "0x744222844bFeCC77156297a6427B5876A6769e19",
"make": {
"assetType": {
"assetClass": "ERC721",
"contract": "0xcfa14f6DC737b8f9e0fC39f05Bf3d903aC5D4575",
"tokenId": 1
},
"value": "1"
},
"take": {
"assetType": {
"assetClass": "ETH"
},
"value": "1000000000000000000"
},
"data": {
"dataType": "RARIBLE_V2_DATA_V1",
"payouts": [],
"originFees": []
},
"salt": "5422",
"signature": "0x45461654b86e856686e7a2e9a9213b29f8dc32a731046e0c2f1aa01e4eaa991e41ebc67535fac14c333ad5b0d0d821ef518edc9ed08ad7efc0af572620c045ce1c"
}
Response
{
"maker": "0x744222844bfecc77156297a6427b5876a6769e19",
"make": {
"assetType": {
"assetClass": "ERC721",
"contract": "0xcfa14f6dc737b8f9e0fc39f05bf3d903ac5d4575",
"tokenId": "1"
},
"value": "1"
},
"take": {
"assetType": {
"assetClass": "ETH"
},
"value": "1000000000000000000"
},
"type": "RARIBLE_V2",
"fill": "0",
"makeStock": "1",
"cancelled": false,
"salt": "0x000000000000000000000000000000000000000000000000000000000000152e",
"data": {
"dataType": "RARIBLE_V2_DATA_V1",
"payouts": [],
"originFees": []
},
"signature": "0x45461654b86e856686e7a2e9a9213b29f8dc32a731046e0c2f1aa01e4eaa991e41ebc67535fac14c333ad5b0d0d821ef518edc9ed08ad7efc0af572620c045ce1c",
"createdAt": "2021-05-25T02:04:28.836+00:00",
"lastUpdateAt": "2021-05-25T02:04:28.983+00:00",
"pending": [],
"hash": "0xc9cb92588fc1434a417f4cacb9e1267750e6f1cbe650988a6fdc1d0be8a310a9",
"makeBalance": "1",
"takePriceUsd": 2735.1365826056253000000000000000000
}
You can only fill an ETH order if your side of the order is providing the ETH. Otherwise you would have to use WETH which has the transferFrom capability.
Encoded order
{
"maker": "0x744222844bfecc77156297a6427b5876a6769e19",
"makeAsset": { "assetType": { "assetClass": "0xaaaebeba", "data": "0x" }, "value": "100000000000000000" },
"taker": "0x0000000000000000000000000000000000000000",
"takeAsset": {
"assetType": {
"assetClass": "0x73ad2146",
"data": "0x000000000000000000000000cfa14f6dc737b8f9e0fc39f05bf3d903ac5d45750000000000000000000000000000000000000000000000000000000000000005"
},
"value": "1"
},
"salt": "0",
"start": "0",
"end": "0",
"dataType": "0x4c234266",
"data": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
Example transaction that has both a signed sell order and an unsigned buy order, submitted by the buyer.
https://rinkeby.etherscan.io/tx/0x2d01b1869b556629acf7304b53d2f77fd927a2c3c9daea02aa093ba8ba41b4c6
A bid is just an unmatched buy order. The seller accepts it by creating a matched order.
Signatures are required to validate the orders. They are only required when the order maker is not the message sender.
This means a buyer can submit an order to the contract without their signature, only the seller's signature. And in return, a seller can submit an order to the contract without their signature, only the buyer's signature.
Sell ERC721 for ETH
{
"types": {
"EIP712Domain": [
{
"type": "string",
"name": "name"
},
{
"type": "string",
"name": "version"
},
{
"type": "uint256",
"name": "chainId"
},
{
"type": "address",
"name": "verifyingContract"
}
],
"AssetType": [
{
"name": "assetClass",
"type": "bytes4"
},
{
"name": "data",
"type": "bytes"
}
],
"Asset": [
{
"name": "assetType",
"type": "AssetType"
},
{
"name": "value",
"type": "uint256"
}
],
"Order": [
{
"name": "maker",
"type": "address"
},
{
"name": "makeAsset",
"type": "Asset"
},
{
"name": "taker",
"type": "address"
},
{
"name": "takeAsset",
"type": "Asset"
},
{
"name": "salt",
"type": "uint256"
},
{
"name": "start",
"type": "uint256"
},
{
"name": "end",
"type": "uint256"
},
{
"name": "dataType",
"type": "bytes4"
},
{
"name": "data",
"type": "bytes"
}
]
},
"domain": {
"name": "Exchange",
"version": "2",
"chainId": 3,
"verifyingContract": "0x1e1B6E13F0eB4C570628589e3c088BC92aD4dB45"
},
"primaryType": "Order",
"message": {
"maker": "0x744222844bfecc77156297a6427b5876a6769e19",
"makeAsset": {
"assetType": {
"assetClass": "0x73ad2146",
"data": "0x000000000000000000000000cfa14f6dc737b8f9e0fc39f05bf3d903ac5d45750000000000000000000000000000000000000000000000000000000000000001"
},
"value": "1"
},
"taker": "0x0000000000000000000000000000000000000000",
"takeAsset": {
"assetType": {
"assetClass": "0xaaaebeba",
"data": "0x"
},
"value": "1000000000000000000"
},
"salt": "6274",
"start": "0",
"end": "0",
"dataType": "0x4c234266",
"data": "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
}
Example implementation: https://rinkeby.etherscan.io/address/0x998Cf6fDd7FF1EB258c288762284142D28D0ee39#code
EIP1271 allows a contract to delegate authorized signers. Usually EIP1271 expects an authorized delegate to submit a signature via the contract, and uses typical ECrecover to see if the signature is valid.
EIP1271 returns valid signature: true**
if the signature is valid & the signer is authorized by the contract
** not actually true
, but instead a magic value to guard against mistaken 'true' bit
The Rarible contract checks if the signature is valid with the following code
require(
ERC1271(order.maker).isValidSignature(_hashTypedDataV4(hash), signature) == MAGICVALUE,
"contract order signature verification error"
);
Rarible calculates 3 types of fees during a transfer: origin, royalties, and protocol
Origin fees are specified for each order
Royalties are defined by the NFT
Protocol fees are defined by the contract.
Example of how to query orders for all NFTs in a collection
curl --location --request GET 'https://api-staging.rarible.com/protocol/v0.1/ethereum/order/orders/sell/byCollection?collection=0xcfa14f6DC737b8f9e0fC39f05Bf3d903aC5D4575&sort=LAST_UPDATE'
Response
{
"orders": [
{
"maker": "0x744222844bfecc77156297a6427b5876a6769e19",
"make": {
"assetType": {
"assetClass": "ERC721",
"contract": "0xcfa14f6dc737b8f9e0fc39f05bf3d903ac5d4575",
"tokenId": "1"
},
"value": "1"
},
"take": {
"assetType": {
"assetClass": "ETH"
},
"value": "1000000000000000000"
},
"type": "RARIBLE_V2",
"fill": "0",
"makeStock": "1",
"cancelled": false,
"salt": "0x00000000000000000000000000000000000000000000000000000000000002b3",
"data": {
"dataType": "RARIBLE_V2_DATA_V1",
"payouts": [],
"originFees": []
},
"signature": "0xf45a9adfb58fdd9da808b5f80302ebdb7d75f032d6aaee9700ee93f567fff3b148116a93ad2bd908a2d8e9576295650460a653d442f7c3472cb8f1275739075e1c",
"createdAt": "2021-05-25T01:33:43.358+00:00",
"lastUpdateAt": "2021-05-25T01:33:43.532+00:00",
"pending": [],
"hash": "0x0b3007a2a4cd8701f98b8fd02f2a9bc09320d11d5e4fa8134f884904d8bfdf3b",
"makeBalance": "1",
"takePriceUsd": 2735.136582605625300000000000000000
}
],
"continuation": "1621906423532_0b3007a2a4cd8701f98b8fd02f2a9bc09320d11d5e4fa8134f884904d8bfdf3b"
}
We use Rarible to help us identify NFTs from creators and collectors in the Picnic showcase. The Rarible API provides a few great endpoints for fetching the necessary data.
The following endpoints can be used:
Paginate through owned tokens
import axios from 'axios';
/**
* Get collector's owned tokens.
* @param {string} owner - owner address (0x...)
* @param {object} opts - options
* @param {string} opts.continuation - Rariable continuation ID
* @param {integer} opts.size - size of tokens to get (default: 100).
* @return
*/
const fetchOwnedTokens = async (owner, opts = {}) => {
const { continuation, size = 100 } = opts;
try {
const result = await axios.get('https://api.rarible.com/protocol/v0.1/ethereum/nft/items/byOwner', {
params: { owner, continuation },
});
const { data } = result;
// Paginate results
let hist = [];
if (data.continuation && data.items.length === size) {
hist = await getOwnedTokens(owner, { ...opts, continuation: data.continuation });
}
// Return full history
return [...data.items, ...hist];
} catch (err) {
console.error(err);
return [];
}
};
The byOwner
endpoint does not return token metadata. You can attempt to query this information from the blockchain directly or use another API to collect token metadata information.
You can use the getItemMetaById
Rarible API endpoint to get token metadata. Be mindful that you’ll have to make one request per token.
import axios from 'axios';
/**
* Get token metadata from token id.
* @param {string} id - token ID, formatted as CONTRACT_ADDRESS:TOKEN_ID (e.g. 0x1:1001)
* @return {object}
*/
const fetchTokenMetadata = async id => {
const { data } = await axios.get(`https://api.rarible.com/protocol/v0.1/ethereum/nft/items/${id}/meta`);
if (!data?.name) {
throw new Error('Invalid NFT data', { id, data });
}
return data;
};
If you have question, please reach out. greg@picnic.show / @gleuch