NFT and Token Operations | XRPL APIs & Integration | XRP Academy - XRP Academy
3 free lessons remaining this month

Free preview access resets monthly

Upgrade for Unlimited
Skip to main content
intermediate50 min

NFT and Token Operations

Learning Objectives

Mint NFTs with appropriate flags and metadata

Create and accept NFT buy/sell offers

Query account NFT holdings and offers using account_nfts and related methods

Manage issued currency tokens through trust lines

Build patterns for NFT marketplace functionality

XRPL NFTs (XLS-20) are native ledger objects:

// NFT properties encoded in NFTokenID
{
  Flags: 8,              // Transferable, burnable, etc.
  Issuer: 'rCreator...', // Original minter
  NFTokenTaxon: 0,       // Collection identifier
  TransferFee: 1000,     // 10% (in basis points, max 50%)
  TokenSequence: 42      // Unique per issuer
}
const NFTFlags = {
  tfBurnable: 0x0001,     // Owner can burn
  tfOnlyXRP: 0x0002,      // Can only be bought with XRP
  tfTrustLine: 0x0004,    // Requires issuer trust line
  tfTransferable: 0x0008  // Can be transferred/sold
}

const mintTx = {
  TransactionType: 'NFTokenMint',
  Account: wallet.address,
  NFTokenTaxon: 0,  // Collection grouping (arbitrary number)
  Flags: NFTFlags.tfTransferable | NFTFlags.tfBurnable,
  TransferFee: 500,  // 5% royalty on secondary sales
  URI: Buffer.from('ipfs://QmExample...').toString('hex').toUpperCase()
}

const result = await client.submitAndWait(mintTx, { wallet })

// Extract minted NFTokenID from metadata
const nftokenID = extractNFTokenID(result.result.meta)
```

function extractNFTokenID(meta) {
  for (const node of meta.AffectedNodes) {
    const created = node.CreatedNode
    if (created?.LedgerEntryType === 'NFTokenPage') {
      const tokens = created.NewFields.NFTokens
      if (tokens && tokens.length > 0) {
        return tokens[tokens.length - 1].NFToken.NFTokenID
      }
    }

const modified = node.ModifiedNode
if (modified?.LedgerEntryType === 'NFTokenPage') {
const prev = modified.PreviousFields?.NFTokens || []
const final = modified.FinalFields?.NFTokens || []
const newTokens = final.filter(t =>
!prev.some(p => p.NFToken.NFTokenID === t.NFToken.NFTokenID)
)
if (newTokens.length > 0) {
return newTokens[0].NFToken.NFTokenID
}
}
}
return null
}
```


const response = await client.request({
  command: 'account_nfts',
  account: 'rOwnerAddress...',
  limit: 100
})

// Response
{
"account_nfts": [
{
"Flags": 8,
"Issuer": "rCreator...",
"NFTokenID": "000800000...",
"NFTokenTaxon": 0,
"URI": "697066733A2F2F...", // Hex-encoded
"nft_serial": 42
}
]
}
```

function decodeNFTUri(hexUri) {
  if (!hexUri) return null
  return Buffer.from(hexUri, 'hex').toString('utf8')
}

const uri = decodeNFTUri(nft.URI)
// "ipfs://QmExample..."
```

// Get sell offers for an NFT
const sellOffers = await client.request({
  command: 'nft_sell_offers',
  nft_id: 'NFTokenID...'
})

// Get buy offers for an NFT
const buyOffers = await client.request({
command: 'nft_buy_offers',
nft_id: 'NFTokenID...'
})
```


const sellOffer = {
  TransactionType: 'NFTokenCreateOffer',
  Account: wallet.address,
  NFTokenID: 'NFTokenID...',
  Amount: xrpl.xrpToDrops('100'),  // Selling for 100 XRP
  Flags: 1  // tfSellNFToken
}

const result = await client.submitAndWait(sellOffer, { wallet })
const offerID = extractOfferID(result.result.meta)
```

const buyOffer = {
  TransactionType: 'NFTokenCreateOffer',
  Account: wallet.address,
  NFTokenID: 'NFTokenID...',
  Owner: 'rCurrentOwner...',  // Required for buy offers
  Amount: xrpl.xrpToDrops('95')  // Offering 95 XRP
  // No tfSellNFToken flag = buy offer
}
// Accept a buy offer (seller executes)
const acceptBuy = {
  TransactionType: 'NFTokenAcceptOffer',
  Account: wallet.address,  // NFT owner
  NFTokenBuyOffer: 'OfferID...'
}

// Accept a sell offer (buyer executes)
const acceptSell = {
TransactionType: 'NFTokenAcceptOffer',
Account: wallet.address, // Buyer
NFTokenSellOffer: 'OfferID...'
}

// Brokered sale (match buy and sell offers)
const brokeredSale = {
TransactionType: 'NFTokenAcceptOffer',
Account: wallet.address, // Broker
NFTokenBuyOffer: 'BuyOfferID...',
NFTokenSellOffer: 'SellOfferID...',
NFTokenBrokerFee: xrpl.xrpToDrops('1') // Optional broker fee
}
```

const burnTx = {
  TransactionType: 'NFTokenBurn',
  Account: wallet.address,
  NFTokenID: 'NFTokenID...'
}

const trustSet = {
  TransactionType: 'TrustSet',
  Account: wallet.address,
  LimitAmount: {
    currency: 'USD',
    issuer: 'rIssuerAddress...',
    value: '10000'  // Maximum you'll hold
  }
}
// Issuer sends tokens to holders who have trust lines
const issueTx = {
  TransactionType: 'Payment',
  Account: issuerWallet.address,
  Destination: 'rHolder...',
  Amount: {
    currency: 'USD',
    issuer: issuerWallet.address,
    value: '1000'
  }
}

class NFTCollectionManager {
  constructor(client, wallet) {
    this.client = client
    this.wallet = wallet
  }

async mint(uri, options = {}) {
const tx = {
TransactionType: 'NFTokenMint',
Account: this.wallet.address,
NFTokenTaxon: options.taxon || 0,
Flags: options.flags || (NFTFlags.tfTransferable | NFTFlags.tfBurnable),
TransferFee: options.transferFee || 0,
URI: Buffer.from(uri).toString('hex').toUpperCase()
}

const result = await this.client.submitAndWait(tx, { wallet: this.wallet })
return extractNFTokenID(result.result.meta)
}

async listForSale(nftID, priceXRP, options = {}) {
const tx = {
TransactionType: 'NFTokenCreateOffer',
Account: this.wallet.address,
NFTokenID: nftID,
Amount: xrpl.xrpToDrops(priceXRP),
Flags: 1, // tfSellNFToken
...(options.destination && { Destination: options.destination }),
...(options.expiration && { Expiration: options.expiration })
}

const result = await this.client.submitAndWait(tx, { wallet: this.wallet })
return extractOfferID(result.result.meta)
}

async getMyNFTs() {
const response = await this.client.request({
command: 'account_nfts',
account: this.wallet.address
})

return response.result.account_nfts.map(nft => ({
id: nft.NFTokenID,
issuer: nft.Issuer,
taxon: nft.NFTokenTaxon,
uri: decodeNFTUri(nft.URI),
serial: nft.nft_serial
}))
}

async getOffers(nftID) {
const [buyOffers, sellOffers] = await Promise.all([
this.client.request({ command: 'nft_buy_offers', nft_id: nftID }).catch(() => ({ result: { offers: [] } })),
this.client.request({ command: 'nft_sell_offers', nft_id: nftID }).catch(() => ({ result: { offers: [] } }))
])

return {
buy: buyOffers.result.offers || [],
sell: sellOffers.result.offers || []
}
}
}
```



Build a complete NFT management system that can mint NFTs, list for sale, query holdings, and accept offers. Test on testnet with at least 3 NFT lifecycle operations.

Time Investment: 2-3 hours


End of Lesson 10

Key Takeaways

1

NFTs are native:

No smart contracts needed; minting, trading, and royalties built into the protocol.

2

Offers are separate objects:

Create offers, then accept; no direct transfer with payment.

3

Reserves matter:

NFT pages and offers consume reserve; factor into costs.

4

Trust lines required for tokens:

Issued currencies need trust lines before receipt. ---