Skip to main content
Glama
mint.ts14.2 kB
import { Client, Wallet } from "xrpl"; import { z } from "zod"; import { server } from "../../server/server.js"; import { getXrplClient } from "../../core/services/clients.js"; import { MAINNET_URL, TESTNET_URL } from "../../core/constants.js"; import { connectedWallet, isConnectedToTestnet } from "../../core/state.js"; server.tool( "nft-mint", "Create a non-fungible token on the XRP Ledger", { fromSeed: z .string() .optional() .describe( "Optional seed of the wallet to use. If not provided, the connected wallet will be used." ), nftokenTaxon: z .number() .describe( "An arbitrary identifier for a collection of related NFTs" ), issuer: z .string() .optional() .describe( "Issuer account (if minting on behalf of another account)" ), transferFee: z .number() .min(0) .max(50000) .optional() .describe( "Fee for secondary sales (0-50000, representing 0.00%-50.00%)" ), uri: z .string() .optional() .describe( "URI pointing to token metadata (up to 256 bytes, will be converted to hex)" ), flags: z .object({ burnable: z .boolean() .optional() .describe("Allow the issuer to burn the token"), onlyXRP: z .boolean() .optional() .describe("The token can only be bought or sold for XRP"), transferable: z .boolean() .optional() .describe("The token can be transferred to others"), mutable: z .boolean() .optional() .describe("The URI field can be updated later"), }) .optional() .describe("Token flags"), amount: z .object({ currency: z.string().describe("Currency code"), issuer: z .string() .optional() .describe("Issuer account address"), value: z.string().describe("Amount value"), }) .optional() .describe("Amount expected for the NFToken"), expiration: z .number() .optional() .describe( "Time after which the offer is no longer active (seconds since Ripple Epoch)" ), destination: z .string() .optional() .describe("Account that may accept this offer"), memos: z .array( z.object({ memoType: z .string() .optional() .describe("Type of memo (hex encoded)"), memoData: z .string() .optional() .describe("Content of memo (hex encoded)"), memoFormat: z .string() .optional() .describe("Format of memo (hex encoded)"), }) ) .optional() .describe("Array of memos to attach to the transaction"), fee: z.string().optional().describe("Transaction fee in XRP"), useTestnet: z .boolean() .optional() .describe( "Whether to use the testnet (true) or mainnet (false). If not provided, uses the network from the connected wallet." ), }, async ({ fromSeed, nftokenTaxon, issuer, transferFee, uri, flags, amount, expiration, destination, memos, fee, useTestnet, }) => { let client: Client | null = null; try { // Determine which network to use const useTestnetNetwork = useTestnet !== undefined ? useTestnet : isConnectedToTestnet; client = await getXrplClient(useTestnetNetwork); // Use provided seed or connected wallet let wallet: Wallet; if (fromSeed) { wallet = Wallet.fromSeed(fromSeed); } else if (connectedWallet) { wallet = connectedWallet; } else { throw new Error( "No wallet connected. Please connect first using connect-to-xrpl tool or provide a fromSeed." ); } // Validate inputs if ( transferFee !== undefined && (!flags || flags.transferable !== true) ) { throw new Error( "TransferFee can only be set if the transferable flag is enabled" ); } if ((destination || expiration) && !amount) { throw new Error( "If destination or expiration is specified, amount must also be specified" ); } // Format amount based on type const formatAmount = (amountObj: any) => { if (typeof amountObj === "string") { // XRP amount in drops return amountObj; } else if (amountObj.mpt_issuance_id) { // MPT payment return { mpt_issuance_id: amountObj.mpt_issuance_id, value: amountObj.value, }; } else if (amountObj.currency.toUpperCase() === "XRP") { // XRP in object form - convert to drops return amountObj.value; } else { // Other issued currency return { currency: amountObj.currency, issuer: amountObj.issuer, value: amountObj.value, }; } }; // Convert URI to hex if provided let uriHex = undefined; if (uri) { // Check if already hex if (/^[0-9a-fA-F]+$/.test(uri)) { uriHex = uri; } else { // Convert string to hex uriHex = Buffer.from(uri).toString("hex"); } // Check length after conversion if (uriHex && uriHex.length > 512) { throw new Error("URI exceeds maximum length of 256 bytes"); } } // Create NFTokenMint transaction const nftokenMintTx: any = { TransactionType: "NFTokenMint", Account: wallet.address, NFTokenTaxon: nftokenTaxon, }; // Add optional fields if provided if (issuer) { nftokenMintTx.Issuer = issuer; } if (transferFee !== undefined) { nftokenMintTx.TransferFee = transferFee; } if (uriHex) { nftokenMintTx.URI = uriHex; } // Set flags based on options if (flags) { let flagsValue = 0; if (flags.burnable === true) { flagsValue |= 0x00000001; // tfBurnable } if (flags.onlyXRP === true) { flagsValue |= 0x00000002; // tfOnlyXRP } if (flags.transferable === true) { flagsValue |= 0x00000008; // tfTransferable } if (flags.mutable === true) { flagsValue |= 0x00000010; // tfMutable } if (flagsValue !== 0) { nftokenMintTx.Flags = flagsValue; } } // Add offer details if provided if (amount) { nftokenMintTx.Amount = formatAmount(amount); } if (expiration !== undefined) { nftokenMintTx.Expiration = expiration; } if (destination) { nftokenMintTx.Destination = destination; } // Add memos if provided if (memos && memos.length > 0) { nftokenMintTx.Memos = memos.map((memo) => { const memoObj: any = { Memo: {} }; if (memo.memoType) { memoObj.Memo.MemoType = memo.memoType; } if (memo.memoData) { memoObj.Memo.MemoData = memo.memoData; } if (memo.memoFormat) { memoObj.Memo.MemoFormat = memo.memoFormat; } return memoObj; }); } // Add optional fee if provided if (fee) { nftokenMintTx.Fee = fee; } // Submit transaction const prepared = await client.autofill(nftokenMintTx); const signed = wallet.sign(prepared); const result = await client.submitAndWait(signed.tx_blob); let status = "unknown"; if (typeof result.result.meta !== "string" && result.result.meta) { status = result.result.meta.TransactionResult === "tesSUCCESS" ? "success" : "failed"; } // Get the NFToken ID from the transaction metadata let nftokenID = null; if ( status === "success" && typeof result.result.meta !== "string" && result.result.meta && result.result.meta.AffectedNodes ) { // Look for created NFToken in the affected nodes const affectedNodes = result.result.meta.AffectedNodes; for (const node of affectedNodes) { // Using type assertion to handle different node types const modifiedNode = node as any; if ( modifiedNode.ModifiedNode && modifiedNode.ModifiedNode.LedgerEntryType === "NFTokenPage" && modifiedNode.ModifiedNode.FinalFields && modifiedNode.ModifiedNode.FinalFields.NFTokens ) { const previousTokens = ( modifiedNode.ModifiedNode.PreviousFields ?.NFTokens || [] ).map((t: any) => t.NFToken.NFTokenID); const finalTokens = ( modifiedNode.ModifiedNode.FinalFields.NFTokens || [] ).map((t: any) => t.NFToken.NFTokenID); // Find new token IDs that weren't in the previous state const newTokenIDs = finalTokens.filter( (id: string) => !previousTokens.includes(id) ); if (newTokenIDs.length > 0) { nftokenID = newTokenIDs[0]; break; } } } } // Get account's NFTs let accountNFTs: any[] = []; if (status === "success") { try { const nftsResponse = await client.request({ command: "account_nfts", account: issuer || wallet.address, ledger_index: "validated", }); if (nftsResponse.result.account_nfts) { accountNFTs = nftsResponse.result.account_nfts; } } catch (error) { console.error("Error fetching account NFTs:", error); } } return { content: [ { type: "text", text: JSON.stringify( { status, hash: result.result.hash, account: wallet.address, issuer: issuer || wallet.address, nftokenTaxon, nftokenID, uri: uriHex, flags: flags || {}, accountNFTs, network: useTestnetNetwork ? TESTNET_URL : MAINNET_URL, networkType: useTestnetNetwork ? "testnet" : "mainnet", result: result.result, }, null, 2 ), }, ], }; } catch (error) { return { content: [ { type: "text", text: `Error minting NFT: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } finally { if (client) { await client.disconnect(); } } } );

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RomThpt/mcp-xrpl'

If you have feedback or need assistance with the MCP directory API, please join our Discord server