HackMD
  • Prime
    Prime  Full-text search on all paid plans
    Search anywhere and reach everything in a Workspace with Prime plan.
    Got it
      • Create new note
      • Create a note from template
    • Prime  Full-text search on all paid plans
      Prime  Full-text search on all paid plans
      Search anywhere and reach everything in a Workspace with Prime plan.
      Got it
      • Sharing Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • More (Comment, Invitee)
      • Publishing
        Everyone on the web can find and read all notes of this public team.
        After the note is published, everyone on the web can find and read this note.
        See all published notes on profile page.
      • Commenting Enable
        Disabled Forbidden Owners Signed-in users Everyone
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Invitee
      • No invitee
      • Options
      • Versions and GitHub Sync
      • Transfer ownership
      • Delete this note
      • Template
      • Save as template
      • Insert from template
      • Export
      • Dropbox
      • Google Drive
      • Gist
      • Import
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
      • Download
      • Markdown
      • HTML
      • Raw HTML
    Menu Sharing Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Versions and GitHub Sync Transfer ownership Delete this note
    Export
    Dropbox Google Drive Gist
    Import
    Dropbox Google Drive Gist Clipboard
    Download
    Markdown HTML Raw HTML
    Back
    Sharing
    Sharing Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Note Permission
    Read
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Write
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    More (Comment, Invitee)
    Publishing
    Everyone on the web can find and read all notes of this public team.
    After the note is published, everyone on the web can find and read this note.
    See all published notes on profile page.
    More (Comment, Invitee)
    Commenting Enable
    Disabled Forbidden Owners Signed-in users Everyone
    Permission
    Owners
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Invitee
    No invitee
       owned this note    owned this note      
    Published Linked with GitHub
    Like BookmarkBookmarked
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # Rarible Hackathon Docs ## Intro to Rarible Protocol Rarible's protocol includes contracts, standards, and APIs for: **Minting** * Minting (Both ERC721 & ERC1155) * Lazy Minting - Token metadata & minting signatures are stored on Rarible back-end until a buyer fills the order. Then a `mintAndTransfer` call is made on chain when the order is filled **Exchange** (Buy, Sell, Bid) * Signature based order matching using an off chain order book * Asset discovery is off chain, then buyers or sellers can submit both sides of an order, including relevant signatures to execute a transfer. * Asset owners must `approve` the Rarible exchange to transfer on their behalf * Multiple asset types are supported to fill orders (ERC721, ERC1155, ERC20) * Bidding is supported **Indexer** * Rarible API exposes ways to query NFTs indexed on Ethereum * Rarible API exposes ways to create orders ### API Reference 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 | ## How to use the starter app The Rarible starter app is forked from Scaffold-Eth https://github.com/ipatka/scaffold-eth/tree/rarible-starter-app - [x] Minting using the standard Rarible collection - [x] Minting using a custom collection - [x] Lazy minting - [x] Creating a sell order for ETH - [x] Creating a sell order for an ERC20 - [x] Creating a sell order for another NFT - [x] Creating a matching buy order for ETH - [x] Creating a matching buy order for an ERC20 - [x] Creating a matching buy order for another NFT ## Contracts | 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 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. ---- ### ERC721 Overview ```solidity mintAndTransfer(LibERC721LazyMint.Mint721Data memory data, address to) ``` **Mint721Data Parameter Structure** ```solidity struct Mint721Data { uint tokenId; string uri; LibPart.Part[] creators; LibPart.Part[] royalties; bytes[] signatures; } ``` ***Parameters*** <details> <summary markdown="span"> tokenId </summary><p> 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](https://api-reference.rarible.com/#operation/generateTokenId) will allow you to get the next available ID. </p> </details> <details> <summary markdown="span"> uri </summary><p> This is the suffix for the tokenURI. The prefix for Rarible protocol contracts is `ipfs://` - Sample IPFS uri: `/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp` - Gets concatenated into the following upon minting: `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. </p> </details> <details> <summary markdown="span"> creators </summary><p> `creators` is an array of addresses and values. The `LibPart.Part` struct it derives from is provided below. ```solidity 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. </p> </details> <details> <summary markdown="span"> royalties </summary><p> `royalties` is an array of addresses and values. Like `creators`, it's also derived from the `LibPart.Part` struct provided below. ```solidity 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. </p> </details> <details> <summary markdown="span"> signatures </summary><p> `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**. </p> </details> <br /> **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" } ``` ### ERC721 Lazy Minting 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](https://github.com/ipatka/scaffold-eth/tree/rarible-starter-app), [here](https://github.com/MysteryDrop/mystery-drop/tree/master/packages/react-app/src/util), and [here](https://github.com/evgenynacu/sign-typed-data). #### For 721 (Ropsten) **Step 1**: Generate a token ID. - Since a lazy mint is stored off-chain, it's necessary to generate a token ID through this API to ensure you get the next available token ID since there's no certainty about when the NFT will actually be minted. 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](https://hackmd.io/ktJuljjGTA2TivezBXKA5g?view#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! 🎉 <br /> ---- ### ERC721 Standard Minting 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)` ```solidity 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](https://corporatefinanceinstitute.com/resources/knowledge/finance/basis-point-beep/) #### Example ```javascript 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); } ``` ---- ### ERC1155 Overview ```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*** <details> <summary markdown="span"> tokenId </summary><p> 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](https://api-reference.rarible.com/#operation/generateTokenId) will allow you to get the next available ID. </p> </details> <details> <summary markdown="span"> uri </summary><p> This is the suffix for the tokenURI. The prefix for Rarible protocol contracts is `ipfs://` - Sample IPFS uri: `/ipfs/QmWLsBu6nS4ovaHbGAXprD1qEssJu4r5taQfB74sCG51tp` - Gets concatenated into the following upon minting: `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. </p> </details> <details> <summary markdown="span"> supply </summary><p> `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). </p> </details> <details> <summary markdown="span"> creators </summary><p> `creators` is an array of addresses and values. The `LibPart.Part` struct it derives from is provided below. ```solidity 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. </p> </details> <details> <summary markdown="span"> royalties </summary><p> `royalties` is an array of addresses and values. Like `creators`, it's also derived from the `LibPart.Part` struct provided below. ```solidity 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. </p> </details> <details> <summary markdown="span"> signatures </summary><p> `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**. </p> </details> <br /> **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" } ``` ### ERC1155 Lazy Minting 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](https://github.com/ipatka/scaffold-eth/tree/rarible-starter-app), [here](https://github.com/MysteryDrop/mystery-drop/tree/master/packages/react-app/src/util), and [here](https://github.com/evgenynacu/sign-typed-data). #### For 1155 (Ropsten) **Step 1**: Generate a token ID. - Since a lazy mint is stored off-chain, it's necessary to generate a token ID through this API to ensure you get the next available token ID since there's no certainty about when the NFT will actually be minted. 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](https://hackmd.io/ktJuljjGTA2TivezBXKA5g?view#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! 🎉 <br /> ---- ### ERC1155 Standard Minting 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)` ```solidity 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](https://corporatefinanceinstitute.com/resources/knowledge/finance/basis-point-beep/) #### Example ```javascript 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); } ``` ---- ### Custom Contracts 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](https://eips.ethereum.org/EIPS/eip-721) or [ERC-1155](https://eips.ethereum.org/EIPS/eip-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](https://docs.openzeppelin.com/contracts/4.x/erc721). 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](https://www.npmjs.com/package/@rarible/royalties) or [here](https://www.npmjs.com/package/@rarible/royalties-upgradeable) 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](https://www.npmjs.com/package/@rarible/lazy-mint). In many cases, it may be easier and faster to just [fork the protocol contracts](https://github.com/rariblecom/protocol-contracts/tree/57043e3f9e93223ef9d65dae351d3c55b34e5bf1/tokens) 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. ---- ## Royalties V2 Rarible defines an interface to query royalties from a contract. This is implemented on the standard [Rarible token contracts](https://github.com/rariblecom/protocol-contracts/blob/57043e3f9e93223ef9d65dae351d3c55b34e5bf1/tokens/contracts/erc-721/ERC721Lazy.sol#L12). It exposes a [method](https://github.com/rariblecom/protocol-contracts/blob/57043e3f9e93223ef9d65dae351d3c55b34e5bf1/royalties/contracts/impl/AbstractRoyalties.sol#L8) 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]; } ``` #### Royalties Registry The exchange contract interacts with the Rarible royalties implementation indirectly through a [Royalty Registry](https://github.com/rariblecom/protocol-contracts/blob/57043e3f9e93223ef9d65dae351d3c55b34e5bf1/royalties-registry/contracts/RoyaltiesRegistry.sol#L58). 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. ## Order Book The general process for completing an order with the Rarible Exchange is as follows: 1. Seller approves the Rarible exchange contract to transfer NFT on their behalf 2. Seller creates and signs an order. They specify the types and amounts of assets they would like in return. 3. Seller submits the order to the indexer 4. Potential buyers query the indexer to get sell orders for a specific item or collection 5. Buyers can create a matching buy order for a sell order from the indexer. The buyer can then submit a matching buy order to the smart contract and execute the transfer (no sig required). 6. Or the buyer can create a new bid and submit it to the indexer (requires signature) 7. If the buyer submits a bid, the seller can choose to accept it by creating the matching sell order and submitting it to the contract, or they can send a signed order back to the buyer which the buyer would submit to the contract. TODO swimlanes ### Order Structure Each order (both buy & sell) consist of a `makeAsset` and a `takeAsset` **Make Asset** is what you are sending. * In a buy order this is what you are *paying* for the seller's NFT. This can be ETH, ERC20, ERC721, ERC1155, or any custom asset using their Asset Matcher interface * In a sell order this is the NFT you are selling **Take Asset** is what you are accepting in return * In a buy order this is the NFT you are buying * In a sell order this is what you are willing to accept. ### Order | 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 | ### Asset | Field | Description | Required | | -------- | -------- | -------- | | assetType | Specifies ETH, specific ERC20, ERC721, ERC1155 | Yes | | value | uint | Yes | ### AssetType | 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 Types 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 #### ERC721 `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", } ``` #### ERC1155 `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", } ``` #### ERC20 `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", } ``` #### ETH `assetClass`: Truncated hash of string "ETH" `data`: 0x `value`: 1->1e18 Input (pre encoding) ``` { assetType: { assetClass: "ETH" }, value: "10000000000000000", }, ``` #### Custom Asset Matcher Any asset can be added `assetClass`: Truncated hash of any string `data`: Whatever is relevant `value`: some uint ### Sell Orders #### Sell ERC721 for ERC20 #### Sell ERC721 for ETH **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 } ``` #### Sell ERC721 for another ERC721 #### Sell ERC721 for ERC1155 ### Buy Orders #### Pay ERC20 for ERC721 #### Pay ETH for ERC721 **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 #### Swap ERC721 for ERC721 #### Pay ERC1155 for ERC721 ### Bid Orders A bid is just an unmatched buy order. The seller accepts it by creating a matched order. ## Signatures 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. ### EIP712 #### Example 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" } } ``` ![](https://i.imgur.com/Ry921oC.png) ### EIP1271 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" ); ``` ## Fees 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. ## Indexing 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" } ``` # Example Projects ## How Picnic uses the Rarible API We use Rarible to help us identify NFTs from creators and collectors in the [Picnic](https://picnic.show) showcase. The [Rarible API](https://api-reference.rarible.com) provides a few great endpoints for fetching the necessary data. ### API Calls The following endpoints can be used: * **Production:** https://api.rarible.com/ * **Staging (Rinkeby/Ropsten):** https://api-staging.rarible.com/ #### Getting Tokens by Owner Paginate through owned tokens ```javascript 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. ```javascript 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](https://twitter.com/gleuch)

    Import from clipboard

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lost their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template is not available.


    Upgrade

    All
    • All
    • Team
    No template found.

    Create custom template


    Upgrade

    Delete template

    Do you really want to delete this template?

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Tutorials

    Book Mode Tutorial

    Slide Mode Tutorial

    YAML Metadata

    Contacts

    Facebook

    Twitter

    Feedback

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions

    Versions and GitHub Sync

    Sign in to link this note to GitHub Learn more
    This note is not linked with GitHub Learn more
     
    Add badge Pull Push GitHub Link Settings
    Upgrade now

    Version named by    

    More Less
    • Edit
    • Delete

    Note content is identical to the latest version.
    Compare with
      Choose a version
      No search result
      Version not found

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub

        Please sign in to GitHub and install the HackMD app on your GitHub repo. Learn more

         Sign in to GitHub

        HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Available push count

        Upgrade

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Upgrade

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully