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
NFTs are native:
No smart contracts needed; minting, trading, and royalties built into the protocol.
Offers are separate objects:
Create offers, then accept; no direct transfer with payment.
Reserves matter:
NFT pages and offers consume reserve; factor into costs.
Trust lines required for tokens:
Issued currencies need trust lines before receipt. ---