XRP Ledger Apex is back in Amsterdam Register Now
Estimated reading time:

25 minutes

Difficulty:

Intermediate

Concepts covered:
  • Burn NFT
  • Mint NFT
  • NFTs
  • Non-fungible Tokens

Key takeaway:

Learn how to mint and burn NFTs on the XRP Ledger. Use the code sandboxes in the lesson to experiment with the XRPL.js library code.

Lesson 4

Mint and burn NFTs

Mint and burn NFTs on the XRP Ledger.

This lesson shows how to:

  1. Mint new non fungible tokens (NFTs).
  2. Get a list of existing NFTs on an account.
  3. Delete (burn) a NFT.

Try editing some code: mint-and-burn-nfts.js

The interactive code example below can be used with any XRP Ledger network: Testnet, Devnet, or Mainnet. When building on your own, you can update the code to choose different or additional XRP Ledger networks.

 

Get Accounts

  1. Open the codesandbox above. We recommend you click “Edit on Codepen” in a new window (link in the top right)
  2. Get test accounts.
    1. If you have existing NFT-Devnet account seeds:
      1. Paste the account seeds in theSeedsfield.
      2. ClickGet Accounts from Seeds.
    2. If you do not have existing NFT-Devnet accounts:
      1. ClickGet New Standby Account.
      2. ClickGet New Operational Account.

Mint an NFToken

To mint a non fungible token object:

  1. Set theFlagsfield. For testing purposes, we recommend setting the value to8. This sets thetsTransferableflag, meaning that the NFToken object can be transferred to another account. Otherwise, the NFToken object can only be transferred back to the issuing account. SeeNFToken Mintfor information about all of the available flags for minting NFTokens.
  2. Enter theToken URL. This is a URI that points to the data or metadata associated with the NFToken object. You can use the sample URI provided if you do not have one of your own.
  3. Enter theTransfer Fee, a percentage of the proceeds from future sales of the NFToken that will be returned to the original creator. This is a value of 0-50000 inclusive, allowing transfer rates between 0.000% and 50.000% in increments of 0.001%. If you do not set theFlagsfield to allow the NFToken to be transferrable, set this field to 0.
  4. ClickMint Token.

Get tokens

ClickGet Tokensto get a list of NFTokens owned by the account.

Burn a token

The current owner of a NFToken can always destroy (orburn)a NFToken object.

To permanently destroy a NFToken:

  1. Enter theToken ID.
  2. ClickBurn Token.

JavaScript code walkthrough: ripplex3-mint-nfts.js

mintToken()

Connect to the ledger and get the account wallets.
Define the transaction.
Convert the hex URI into a string with the convertStringToHex utility.
If you want the NFToken to be transferable to third parties, set the Flags field to 8.
The Transfer Fee is a value 0 to 50000, used to set a royalty of 0.000% to 50.000% in increments of 0.001.
The TokenTaxon is a required value. It is an arbitrary value defined by the issuer. If you do not have a use for the field, you can set it to 0.
Send the transaction and wait for the response.
Request a list of NFTs owned by the account.
Report the results.
Disconnect from the ledger.
// *******************************************************
// ********************** Mint Token *********************
// *******************************************************
      
async function mintToken() {
  results = 'Connecting to ' + getNet() + '....'
  standbyResultField.value = results
  let net = getNet()
  const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
  const client = new xrpl.Client(net)
  await client.connect()
  results += 'nConnected. Minting NFToken.'
  standbyResultField.value = results
      
  // Note that you must convert the token URL to a hexadecimal 
  // value for this transaction.
  // ------------------------------------------------------------------------
  const transactionBlob = {
    "TransactionType": "NFTokenMint",
    "Account": standby_wallet.classicAddress,
    "URI": xrpl.convertStringToHex(standbyTokenUrlField.value),

    "Flags": parseInt(standbyFlagsField.value),

    "TransferFee": parseInt(standbyTransferFeeField.value),

    "NFTokenTaxon": 0 //Required, but if you have no use for it, set to zero.
  }

  // ----------------------------------------------------- Submit signed blob 
  const tx = await client.submitAndWait(transactionBlob, { wallet: standby_wallet} )
  const nfts = await client.request({
    method: "account_nfts",
    account: standby_wallet.classicAddress  
  })
        
  // ------------------------------------------------------- Report results
  results += 'nnTransaction result: '+ tx.result.meta.TransactionResult
  results += 'nnnfts: ' + JSON.stringify(nfts, null, 2)
  standbyBalanceField.value = 
    (await client.getXrpBalance(standby_wallet.address))
  standbyResultField.value = results    
  client.disconnect()
} //End of mintToken()

getTokens()

Connect to the ledger and get the account wallet.
Request a list of NFTs owned by the account.
Report the results.
Disconnect from the ledger.
// *******************************************************
// ******************* Get Tokens ************************
// *******************************************************
      
async function getTokens() {
  const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
  let net = getNet()
  const client = new xrpl.Client(net)
  results = 'Connecting to ' + net + '...'
  standbyResultField.value = results
  await client.connect()
  results += 'nConnected. Getting NFTokens...'
  standbyResultField.value = results
  const nfts = await client.request({
    method: "account_nfts",
    account: standby_wallet.classicAddress  
  })
  results += 'nNFTs:n ' + JSON.stringify(nfts,null,2)
  standbyResultField.value = results
  client.disconnect()
} //End of getTokens()

burnToken()

Connect to the ledger and get the account wallets.
Define the transaction.
Submit the transaction and wait for the results.
Request a list of NFTokens owned by the client.
Report the results.
Disconnect from the ledger.
// *******************************************************
// ********************* Burn Token **********************
// *******************************************************
      
async function burnToken() {
  const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
  let net = getNet()
  const client = new xrpl.Client(net)
  results = 'Connecting to ' + net + '...'
  standbyResultField.value = results
  await client.connect()
  results += 'nConnected. Burning NFToken...'
  standbyResultField.value = results

  // ------------------------------------------------------- Prepare transaction
  const transactionBlob = {
    "TransactionType": "NFTokenBurn",
    "Account": standby_wallet.classicAddress,
    "NFTokenID": standbyTokenIdField.value
  }

  //---------------------------------- Submit transaction and wait for the results
  const tx = await client.submitAndWait(transactionBlob,{wallet: standby_wallet})
  const nfts = await client.request({
    method: "account_nfts",
    account: standby_wallet.classicAddress  
  })
  results += 'nTransaction result: '+ tx.result.meta.TransactionResult
  results += 'nBalance changes: ' +
  JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
  standbyResultField.value = results
  standbyBalanceField.value = 
    (await client.getXrpBalance(standby_wallet.address))
  results += 'nNFTs: n' + JSON.stringify(nfts,null,2)
  standbyResultField.value = results
  client.disconnect()
}// End of burnToken()

Reciprocal transactions oPmintToken(), oPgetTokens() and oPburnToken()

// **********************************************************************
// ****** Reciprocal Transactions ***************************************
// **********************************************************************
   
// *******************************************************
// ************** Operational Mint Token *****************
// *******************************************************
      
async function oPmintToken() {
  results = 'Connecting to ' + getNet() + '....'
  operationalResultField.value = results
  let net = getNet()
  const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
  const client = new xrpl.Client(net)
  await client.connect()
  results += 'nConnected. Minting NFToken.'
  operationalResultField.value = results

  // Note that you must convert the token URL to a hexadecimal 
  // value for this transaction.
  // ------------------------------------------------------------------------
  const transactionBlob = {
    "TransactionType": 'NFTokenMint',
    "Account": operational_wallet.classicAddress,
    "URI": xrpl.convertStringToHex(operationalTokenUrlField.value),
    "Flags": parseInt(operationalFlagsField.value),
    "TransferFee": parseInt(operationalTransferFeeField.value),
    "NFTokenTaxon": 0 //Required, but if you have no use for it, set to zero.
  }
      
  // ----------------------------------------------------- Submit signed blob 
  const tx = await client.submitAndWait(transactionBlob, { wallet: operational_wallet} )
  const nfts = await client.request({
    method: "account_nfts",
    account: operational_wallet.classicAddress  
  })
        
  // ------------------------------------------------------- Report results
  results += 'nnTransaction result: '+ tx.result.meta.TransactionResult
  results += 'nnnfts: ' + JSON.stringify(nfts, null, 2)
  operationalBalanceField.value = 
    (await client.getXrpBalance(operational_wallet.address))
  operationalResultField.value = results    

  client.disconnect()
} //End of oPmintToken
      
// *******************************************************
// ************** Operational Get Tokens *****************
// *******************************************************

async function oPgetTokens() {
  const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
  let net = getNet()
  const client = new xrpl.Client(net)
  results = 'Connecting to ' + getNet() + '...'
 operationalResultField.value = results
 await client.connect()
  results += 'nConnected. Getting NFTokens...'
  operationalResultField.value = results
  const nfts = await client.request({
    method: "account_nfts",
    account: operational_wallet.classicAddress  
  })
  results += 'nNFTs:n ' + JSON.stringify(nfts,null,2)
  operationalResultField.value = results
  client.disconnect()
} //End of oPgetTokens
      
// *******************************************************
// ************* Operational Burn Token ******************
// *******************************************************
      
async function oPburnToken() {
  const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
  let net = getNet()
  const client = new xrpl.Client(net)
  results = 'Connecting to ' + getNet() + '...'
  operationalResultField.value = results
  await client.connect()
  results += 'nConnected. Burning NFToken...'
  operationalResultField.value = results
      
  // ------------------------------------------------------- Prepare transaction
  const transactionBlob = {
    "TransactionType": "NFTokenBurn",
    "Account": operational_wallet.classicAddress,
    "NFTokenID": operationalTokenIdField.value
  }
      
  //-------------------------------------------------------- Submit signed blob
  const tx = await client.submitAndWait(transactionBlob,{wallet: operational_wallet})
  const nfts = await client.request({
    method: "account_nfts",
    account: operational_wallet.classicAddress  
  })
  results += 'nTransaction result: '+ tx.result.meta.TransactionResult
  results += 'nBalance changes: ' +
    JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
  operationalResultField.value = results
  operationalBalanceField.value = 
    (await client.getXrpBalance(operational_wallet.address))
  operationalBalanceField.value = 
    (await client.getXrpBalance(operational_wallet.address))
  results += 'nNFTs: n' + JSON.stringify(nfts,null,2)
  operationalResultField.value = results
  client.disconnect()
}
// End of oPburnToken()

HTML form preview: 3.mint-nfts.html

Bold text in the following indicates changes to the form that support the new functions.

<html>
  <head>
    <title>Token Test Harness</title>
    <link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
    <style>
       body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
       h1{font-weight: bold;}
       input, button {padding: 6px;margin-bottom: 8px;}
       button{font-weight: bold;font-family: "Work Sans", sans-serif;}
       td{vertical-align: top;padding-right:10px;}
    </style>
    <script src='https://unpkg.com/xrpl@2.2.3'></script>
    <script>
      if (typeof module !== "undefined") {
        const xrpl = require('xrpl')
      }
    </script>
  </head>
  
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->

  <body>
    <h1>Token Test Harness</h1>
    <form id="theForm">
      Choose your ledger instance:  
      <input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233" >
      <label for="testnet">Testnet</label>
      
      <input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
      <label for="devnet">Devnet</label>
      <br/><br/>
      <button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
      <br/>
      <textarea id="seeds" cols="40" rows= "2"></textarea>
      <br/><br/>
      <table>
        <tr valign="top">
          <td>
            <button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
            <table>
              <tr valign="top">
                <td align="left">
                  Standby Account<br/> <input type="text" id="standbyAccountField" size="30" />
                </td>
                <td></td>
              </tr>
              <tr>
                <td align="left">
                  Public Key<br/>
                  <input type="text" id="standbyPubKeyField" size="30"></input>                                                 </td>
                <td align="left">
                  Private Key<br/>
                  <input type="text" id="standbyPrivKeyField" size="30"></input>
                </td>
              </tr>
              <tr>
                <td align="left">
                  Seed <br/>
                  <input type="text" id="standbySeedField" size="30"></input>
                  <br>
                </td>
                <td align="left">
                  XRP Balance <br/>
                  <input type="text" id="standbyBalanceField" size="30"></input>
                </td>
            </tr>
            <tr>
                <td align="left">
                  Amount<br/>
                  <input type="text" id="standbyAmountField" size="30"></input>
                </td>
                <td align="left">
                  Destination Account <br/>
                <input type="text" id="standbyDestinationField" size="30"></input>
                </td>
              </tr>
              <tr valign="top">
                <td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button><br/>
                  <input type="checkbox" id="standbyDefault" checked="true"/>
                  <label for="standbyDefault">Allow Rippling</label>
                </td>

                <td>
                  Currency<br/>
                  <input type="text" id="standbyCurrencyField" size="30" value="USD"></input>
                </td>
              </tr>
              <tr>
                <td colspan=2>NFToken URL<br/>
                  <input type="text" id="standbyTokenUrlField"
                  value = "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf4dfuylqabf3oclgtqy55fbzdi" size="70"/>
                </td>
              </tr>
              <tr>
                <td colspan=2>Flags<br/>
                  <input type="text" id="standbyFlagsField" value="1" size="10"/></td>
              </tr>
              <tr>
                <td colspan=2>NFToken ID<br/>
                  <input type="text" id="standbyTokenIdField" value="" size="70"/></td>
              </tr>
              <tr>
                <td colspan=2>Transfer Fee<br/>
                  <input type="text" id="standbyTransferFeeField" value="" size="70"/></td>
              </tr>
              <tr>
                <td colspan=2>
                  <p align="right">
                    <button type="button" onClick="sendXRP()">Send XRP ↓</button>
                    <button type="button" onClick="createTrustline()">Create TrustLine</button>
                    <button type="button" onClick="sendCurrency()">Send Currency</button>
                    <button type="button" onClick="getBalances()">Get Balances</button>
                    <br/>
                    <button type="button" onClick="mintToken()">Mint NFToken</button>
                    <button type="button" onClick="getTokens()">Get NFTokens</button>
                    <button type="button" onClick="burnToken()">Burn NFToken</button>
                  </p>
                </td>
              </tr>
            </table>
          </td>
          <td valign="top">
            <textarea id="standbyResultField" cols="60" rows="20" ></textarea>
          </td>
        </tr>
      </table>
      <br/><br/>
      <table>
        <tr valign="top">
          <td>
            <button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
            <table>
              <tr valign="top">
                <td align="left">
                  Operational Account<br/> <input type="text" id="operationalAccountField" size="30" />
                </td>
                <td></td>
              </tr>
              <tr>
                <td align="left">Public Key<br/>
                  <input type="text" id="operationalPubKeyField" size="30" />                                                 </td>
                <td align="left">
                  Private Key<br/>
                  <input type="text" id="operationalPrivKeyField" size="30"></input>
                </td>
              </tr>
              <tr>
                <td align="left">
                  Seed <br/>
                  <input type="text" id="operationalSeedField" size="30"></input>
                  <br>
                </td>
                <td align="left">
                  XRP Balance <br/>
                  <input type="text" id="operationalBalanceField" size="30" />
                </td>
            </tr>
            <tr>
                <td align="left">
                  Amount<br/>
                  <input type="text" id="operationalAmountField" size="30" />
                </td>
                <td align="left">
                  Destination Account <br/>
                <input type="text" id="operationalDestinationField" size="30" />
                </td>
              </tr>
              <tr valign="top">
                <td><button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button><br/>
                  <input type="checkbox" id="operatoinalDefault" checked="true"/>
                  <label for="operationalDefault">Allow Rippling</label>
                </td>

                <td>
                  Currency<br/>
                  <input type="text" id="operationalCurrencyField" size="30" value="USD"></input>
                </td>
              </tr>
                            <tr>
                <td colspan=2>NFToken URL<br/>
                  <input type="text" id="operationalTokenUrlField"
                  value = "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf4dfuylqabf3oclgtqy55fbzdi" size="70"/>
                </td>
              </tr>
              <tr>
                <td colspan=2>Flags<br/>
                  <input type="text" id="operationalFlagsField" value="1" size="10"/></td>
              </tr>
              <tr>
                <td colspan=2>NFToken ID<br/>
                  <input type="text" id="operationalTokenIdField" value="" size="70"/></td>
              </tr>
              <tr>
                <td colspan=2>Transfer Fee<br/>
                  <input type="text" id="operationalTransferFeeField" value="" size="70"/></td>
              </tr>
              <tr>
                <td colspan=2>
                  <p align="right">
                    <button type="button" onClick="oPsendXRP()">Send XRP ↑</button>
                    <button type="button" onClick="oPcreateTrustline()">Create TrustLine</button>
                    <button type="button" onClick="oPsendCurrency()">Send Currency</button>
                    <button type="button" onClick="getBalances()">Get Balances</button>
                    <br/>
                    <button type="button" onClick="oPmintToken()">Mint NFToken</button>
                    <button type="button" onClick="oPgetTokens()">Get NFTokens</button>
                    <button type="button" onClick="oPburnToken()">Burn NFToken</button>
                  </p>
                </td>
              </tr>
            </table>
          </td>
          <td>
            <textarea id="operationalResultField" cols="60" rows="20" ></textarea>
          </td>
        </tr>
      </table>
    </form>
  </body>
  <script src='ripplex1-send-xrp.js' async></script>
  <script src='ripplex2-send-currency.js' async></script>
  <script src='ripplex3-mint-nfts.js' async></script>
</html>

Additional resources

END OF LESSON

Now that you've learned a bit about minting and burning NFT's on the XRP Ledger, test your knowledge with a quiz.

What is the NFTokenTaxon ID used for?
A TransferFee of 5000 denotes what fee?
The URI of an NFToken is meant to store what information?