diff --git a/_code-samples/nft-modular-tutorials/account-support.js b/_code-samples/nft-modular-tutorials/account-support.js new file mode 100644 index 0000000000..e235826ec7 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/account-support.js @@ -0,0 +1,207 @@ +// ****************************************************** +// ************* Get the Preferred Network ************** +// ****************************************************** + +function getNet() { + let net + if (document.getElementById("tn").checked) net = "wss://s.altnet.rippletest.net:51233/" + if (document.getElementById("dn").checked) net = "wss://s.devnet.rippletest.net:51233/" + return net +} // End of getNet() + +// ******************************************************* +// ************* Get Account ***************************** +// ******************************************************* + +async function getAccount() { + let net = getNet() + const client = new xrpl.Client(net) + await client.connect() + resultField.value = `===Getting Account===\n\nConnected to ${net}.` + try { + let faucetHost = null + const my_wallet = (await client.fundWallet(null, { faucetHost})).wallet + const newAccount = [my_wallet.address, my_wallet.seed] + return (newAccount) + } + catch (error) { + console.error('===Error getting account:', error); + results += `\nError: ${error.message}\n` + resultField.value = results + throw error; // Re-throw the error to be handled by the caller + } + finally { + // Disconnect from the client + await client.disconnect(); + } +} // End of getAccount() + +async function getNewAccount1() { + account1address.value = "=== Getting new account. ===\n\n" + account1seed.value = "" + const accountInfo= await getAccount() + account1address.value = accountInfo[0] + account1seed.value = accountInfo[1] +} + +async function getNewAccount2() { + account2address.value = "=== Getting new account. ===\n\n" + account2seed.value = "" + const accountInfo= await getAccount() + account2address.value = accountInfo[0] + account2seed.value = accountInfo[1] +} + +// ***************************************************** +// ********** Get Account from Seed ******************** +// ***************************************************** + +async function getAccountFromSeed(my_seed) { + const net = getNet() + const client = new xrpl.Client(net) + await client.connect() + let results = '===Finding wallet.===\n\n' + resultField.value = results + try { + const wallet = xrpl.Wallet.fromSeed(my_seed) + const address = wallet.address + results += "===Wallet found.===\n\n" + results += "Account address: " + address + "\n\n" + resultField.value = results + return (address) + } + catch (error) { + console.error('===Error getting account from seed:', error); + results += `\nError: ${error.message}\n` + resultField.value = results + throw error; // Re-throw the error to be handled by the caller + } + finally { + // Disconnect from the client + await client.disconnect(); + } +} // End of getAccountFromSeed() + +// ***************************************************** +// ********** Get Account from Seed1 ******************* +// ***************************************************** + +async function getAccountFromSeed1() { + account1address.value = await getAccountFromSeed(account1seed.value) +} + +// ***************************************************** +// ********** Get Account from Seed2 ******************* +// ***************************************************** + +async function getAccountFromSeed2() { + account2address.value = await getAccountFromSeed(account2seed.value) +} + +// ***************************************************** +// ************ Gather Account Info ******************** +// ***************************************************** + +function gatherAccountInfo() { + let accountData = account1name.value + "\n" + account1address.value + "\n" + account1seed.value + "\n" + accountData += account2name.value + "\n" + account2address.value + "\n" + account2seed.value + resultField.value = accountData +} + +// ***************************************************** +// ********** Distribute Account Info ****************** +// ***************************************************** + +function distributeAccountInfo() { + let accountInfo = resultField.value.split("\n") + account1name.value = accountInfo[0] + account1address.value = accountInfo[1] + account1seed.value = accountInfo[2] + account2name.value = accountInfo[3] + account2address.value = accountInfo[4] + account2seed.value = accountInfo[5] +} + +// ***************************************************** +// ************ Populate Active Form 1 ***************** +// ***************************************************** + +function populate1() { + accountNameField.value = account1name.value + accountAddressField.value = account1address.value + accountSeedField.value = account1seed.value + getXrpBalance() +} + +// ***************************************************** +// ************ Populate Active Form 2 ***************** +// ***************************************************** + +function populate2() { + accountNameField.value = account2name.value + accountAddressField.value = account2address.value + accountSeedField.value = account2seed.value + getXrpBalance() +} + +// ******************************************************* +// **************** Get XRP Balance ********************* +// ******************************************************* + +async function getXrpBalance() { + const net = getNet() + const client = new xrpl.Client(net) + await client.connect() + let results = `\n===Getting XRP balance...===\n\n` + resultField.value = results + try { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value) + const balance = await client.getXrpBalance(wallet.address) + results += accountNameField.value + " current XRP balance: " + balance + "\n\n" + xrpBalanceField.value = await client.getXrpBalance(accountAddressField.value) + resultField.value = results + } + catch (error) { + console.error('Error getting XRP balance:', error); + results += `\nError: ${error.message}\n` + resultField.value = results + throw error; // Re-throw the error to be handled by the caller + } + finally { + // Disconnect from the client + await client.disconnect(); + } +} // End of getXrpBalance() + +// ******************************************************* +// ************** Get Token Balance ********************* +// ******************************************************* + +async function getTokenBalance() { + let net = getNet() + const client = new xrpl.Client(net) + await client.connect() + let results = `===Connected to ${net}.===\n===Getting account token balance...===\n\n` + resultField.value += results + try { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value) + const balance = await client.request({ + command: "gateway_balances", + account: wallet.address, + ledger_index: "validated", + }) + results = accountNameField.value + "\'s token balance(s): " + JSON.stringify(balance.result, null, 2) + "\n" + resultField.value += results + xrpBalanceField.value = (await client.getXrpBalance(wallet.address)) + } + catch (error) { + console.error('Error getting token balance:', error); + results = `\nError: ${error.message}\n` + resultField.value += results + throw error; // Re-throw the error to be handled by the caller + } + finally { + // Disconnect from the client + await client.disconnect(); + } +} // End of getTokenBalance() diff --git a/_code-samples/nft-modular-tutorials/authorized-minter.html b/_code-samples/nft-modular-tutorials/authorized-minter.html new file mode 100644 index 0000000000..0ad057d374 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/authorized-minter.html @@ -0,0 +1,330 @@ + + + Authorize Minter of NFTs + + + + + + + + + +

Authorize Minter of NFTs

+
+ + Choose your ledger instance: + +    + + +    + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +

+ +

+ + + + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + + + + +    + +
+ + + +

+
+ + + +
+ + + + + + + +
+ + + + + +
+
+ +
+ + + + + +
+
+ + + + + + + +
+ + + + + +
+
+ +
+ + + + + +
+ + + + + +
+ + + + + +
+

+ +

+
+
+ +
+
+ + + diff --git a/_code-samples/nft-modular-tutorials/authorized-minter.js b/_code-samples/nft-modular-tutorials/authorized-minter.js new file mode 100644 index 0000000000..82a3a242f6 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/authorized-minter.js @@ -0,0 +1,91 @@ +// ******************************************************* +// ************ Authorize Minter *********************** +// ******************************************************* + +async function authorizeMinter() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + const net = getNet(); + const client = new xrpl.Client(net); + let results = `\n=== Connected. Authorizing Minter. ===`; + resultField.value = results; + + try { + await client.connect(); + tx_json = { + "TransactionType": "AccountSet", + "Account": wallet.address, + "NFTokenMinter": authorizedMinterField.value, + "SetFlag": xrpl.AccountSetAsfFlags.asfAuthorizedNFTokenMinter + } + + const prepared = await client.autofill(tx_json) + const signed = wallet.sign(prepared) + const result = await client.submitAndWait(signed.tx_blob) + results += '\nAccount setting succeeded.\n' + results += JSON.stringify(result, null, 2) + resultField.value = results + } catch (error) { + console.error("Error setting minter:", error); + results = `\n\n=== Error setting minter: ${error.message}`; + resultField.value += results; + } finally { + if (client && client.isConnected()) { + await client.disconnect(); + } + } +} // End of configureAccount() + +// ******************************************************* +// **************** Mint Other ************************* +// ******************************************************* + +async function mintOther() { + let results = 'Connecting to ' + getNet() + '....' + resultField.value = results + const net = getNet() + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value) + const client = new xrpl.Client(net) + + try { + await client.connect() + results += '\nConnected. Minting NFT.' + resultField.value = results + + // This version adds the "Issuer" field. + // ------------------------------------------------------------------------ + const tx_json = { + "TransactionType": "NFTokenMint", + "Account": wallet.classicAddress, + "URI": xrpl.convertStringToHex(nftURLfield.value), + "Flags": parseInt(flagsField.value), + "TransferFee": parseInt(transferFeeField.value), + "Issuer": issuerField.value, + "NFTokenTaxon": 0 //Required, but if you have no use for it, set to zero. + } + + // ----------------------------------------------------- Submit transaction + const tx = await client.submitAndWait(tx_json, { wallet: wallet }) + const nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress + }) + + // ------------------------------------------------------- Report results + results += '\n\n=== Transaction result: ' + tx.result.meta.TransactionResult + results += '\n\n=== NFTs: ' + JSON.stringify(nfts, null, 2) + resultField.value = results + (await client.getXrpBalance(wallet.address)) + // The line below seems redundant if the previous line already updates resultField.value + // resultField.value = results + } catch (error) { + results += '\n\nAn error occurred: ' + error.message + console.error(error) // Log the error for debugging + resultField.value = results + } finally { + if (client.isConnected()) { // Check if the client is connected before attempting to disconnect + client.disconnect() + results += '\nDisconnected from XRPL.' + resultField.value = results + } + } +} //End of mintOther() + diff --git a/_code-samples/nft-modular-tutorials/batch-minting.html b/_code-samples/nft-modular-tutorials/batch-minting.html new file mode 100644 index 0000000000..784d5656d5 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/batch-minting.html @@ -0,0 +1,320 @@ + + + Batch Mint NFTs + + + + + + + + + +

Batch Mint NFTs

+
+ + Choose your ledger instance: + +    + + +    + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +

+ +

+ + + + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + + + + +    + +
+ + + +

+
+ + + +
+ + + + + + + +
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + +
+ + + + + +
+

+ +

+
+
+ +
+
+ + + diff --git a/_code-samples/nft-modular-tutorials/batch-minting.js b/_code-samples/nft-modular-tutorials/batch-minting.js new file mode 100644 index 0000000000..0c5407f89b --- /dev/null +++ b/_code-samples/nft-modular-tutorials/batch-minting.js @@ -0,0 +1,292 @@ +// ******************************************************* +// ****************** Batch Mint *********************** +// ******************************************************* + +async function batchMintNFTs() { + let client; // Declare client here so it's accessible in finally block + try { + //--------------------- Connect to the XRP Ledger and get the account wallet. + let net = getNet(); + client = new xrpl.Client(net); // Assign client + results = 'Connecting to ' + getNet() + '....'; + resultField.value = results; + await client.connect(); + results += '\nConnected, finding wallet.'; + resultField.value = results; + + let wallet; + try { + wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + } catch (error) { + results += '\nError: Invalid account seed. Please check your seed.'; + resultField.value = results; + return; // Stop execution if wallet cannot be derived + } + resultField.value = results; + + //----------------- Get account information, particularly the Sequence number. + let account_info; + try { + account_info = await client.request({ + "command": "account_info", + "account": wallet.address + }); + } catch (error) { + results += `\nError retrieving account info for ${wallet.address}: ${error.message}`; + resultField.value = results; + return; + } + + let my_sequence = account_info.result.account_data.Sequence; + results += "\n\nSequence Number: " + my_sequence + "\n\n"; + resultField.value = results; + + /* ################################### + Create ticket numbers for the batch + + Without tickets, if one transaction fails, all others in the batch fail. + With tickets, there can be failures, but the rest will continue, and you + can investigate any problems afterward. + */ + + //---------------------- Parse the requested number from nftCountField. + const nftCount = parseInt(nftCountField.value); + if (isNaN(nftCount) || nftCount <= 0) { + results += '\nError: Please enter a valid number of NFTs to mint.'; + resultField.value = results; + return; + } + + //-------------------------------------------- Create the transaction hash. + let ticketTransaction; + try { + ticketTransaction = await client.autofill({ + "TransactionType": "TicketCreate", + "Account": wallet.address, + "TicketCount": nftCount, + "Sequence": my_sequence + }); + } catch (error) { + results += `\nError autofilling ticket creation transaction: ${error.message}`; + resultField.value = results; + return; + } + + + //---------------------------------------------------- Sign the transaction. + const signedTransaction = wallet.sign(ticketTransaction); + + //-------------------------- Submit the transaction and wait for the result. + let tx; + try { + tx = await client.submitAndWait(signedTransaction.tx_blob); + } catch (error) { + results += `\nError submitting ticket creation transaction: ${error.message}`; + resultField.value = results; + return; + } + + if (tx.result.meta.TransactionResult !== 'tesSUCCESS') { + results += `\nError creating tickets. Transaction failed with result: ${tx.result.meta.TransactionResult}`; + resultField.value = results; + return; + } + results += `\nTickets created successfully. Transaction result: ${tx.result.meta.TransactionResult}\n\n`; + resultField.value = results; + + let response; + try { + response = await client.request({ + "command": "account_objects", + "account": wallet.address, + "type": "ticket" + }); + } catch (error) { + results += `\nError retrieving account tickets: ${error.message}`; + resultField.value = results; + return; + } + + //------------------------------------ Populate the tickets array variable. + let tickets = []; + if (response.result.account_objects && response.result.account_objects.length > 0) { + for (let i = 0; i < nftCount; i++) { + if (response.result.account_objects[i]) { + tickets[i] = response.result.account_objects[i].TicketSequence; + } else { + results += `\nWarning: Fewer tickets found than requested. Expected ${nftCount}, found ${response.result.account_objects.length}.`; + resultField.value = results; + break; // Exit loop if tickets run out + } + } + } else { + results += '\nError: No tickets found for the account.'; + resultField.value = results; + return; + } + + //-------------------------------------------------------- Report progress. + results += "Tickets generated, minting NFTs.\n\n"; + resultField.value = results; + + // ################################### + // Mint NFTs + + let mintedNFTsCount = 0; + for (let i = 0; i < tickets.length; i++) { + const transactionParams = { + "TransactionType": "NFTokenMint", + "Account": wallet.classicAddress, + "URI": xrpl.convertStringToHex(nftURLfield.value), + "Flags": parseInt(flagsField.value), + "TransferFee": parseInt(transferFeeField.value), + "Sequence": 0, // Sequence is 0 when using TicketSequence + "TicketSequence": tickets[i], + "LastLedgerSequence": null, // Optional, can be used for time limits + "NFTokenTaxon": nftTaxonField.value, + }; + + if (amountField.value) { + const amount = currencyField.value === "XRP" ? + xrpl.xrpToDrops(amountField.value) : // Use xrpToDrops for XRP + { + currency: currencyField.value, + issuer: issuerField.value, + value: amountField.value, + }; + transactionParams.Amount = amount; + } + + if (expirationField.value) { + const days = parseInt(expirationField.value, 10); + if (isNaN(days) || days <= 0) { + results += `\nWarning: Invalid expiration days for NFT ${i+1}. Skipping expiration for this NFT.`; + resultField.value = results; + } else { + const expirationDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000); + transactionParams.Expiration = xrpl.isoTimeToRippleTime(expirationDate); + } + } + + if (destinationField.value) { + // Basic XRP address validation + if (!xrpl.isValidAddress(destinationField.value)) { + results += `\nWarning: Invalid destination address for NFT ${i+1}. Skipping destination for this NFT.`; + resultField.value = results; + } else { + transactionParams.Destination = destinationField.value; + } + } + + try { + const mintTx = await client.submit(transactionParams, { + wallet: wallet + }); + results += `\nNFT ${i+1} minted successfully.`; + mintedNFTsCount++; + console.log(mintTx.result.nfts) + resultField.value = results; + } catch (error) { + console.log(error); + } + // Add a small delay to avoid hitting rate limits if many NFTs are being minted + await new Promise(resolve => setTimeout(resolve, 500)); + } + + results += `\n\nAttempted to mint ${nftCount} NFTs. Successfully minted ${mintedNFTsCount} NFTs.`; + + results += "\n\nFetching minted NFTs...\n"; + let nfts; + try { + nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress, + limit: 400 + }); + results += JSON.stringify(nfts, null, 2); + + while (nfts.result.marker) { + nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress, + limit: 400, + marker: nfts.result.marker + }); + results += '\n' + JSON.stringify(nfts, null, 2); + } + } catch (error) { + results += `\nError fetching account NFTs: ${error.message}`; + } + + try { + xrpBalanceField.value = (await client.getXrpBalance(wallet.address)); + } catch (error) { + results += `\nError fetching XRP balance: ${error.message}`; + } + + resultField.value = results; + + } catch (error) { + results += `\nAn unexpected error occurred during batch minting: ${error.message}`; + resultField.value = results; + } finally { + if (client && client.isConnected()) { + client.disconnect(); + results += '\nDisconnected from XRP Ledger.'; + resultField.value = results; + } + } +} // End of batchMint() + + +// ******************************************************* +// **************** Get Batch Tokens ********************* +// ******************************************************* + +async function getBatchNFTs() { + let client; // Declare client here for finally block access + try { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + let net = getNet(); + client = new xrpl.Client(net); // Assign client + results = 'Connecting to ' + net + '...'; + resultField.value = results; + await client.connect(); + results += '\nConnected. Getting NFTs...'; + resultField.value = results; + + results += "\n\nNFTs:\n"; + let nfts; + try { + nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress, + limit: 400 + }); + + results += JSON.stringify(nfts, null, 2); + while (nfts.result.marker) { + nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress, + limit: 400, + marker: nfts.result.marker + }); + results += '\n' + JSON.stringify(nfts, null, 2); + } + } catch (error) { + results += `\nError fetching account NFTs: ${error.message}`; + } + resultField.value = results; + + } catch (error) { + results += `\nAn unexpected error occurred while getting batch NFTs: ${error.message}`; + resultField.value = results; + } finally { + if (client && client.isConnected()) { + client.disconnect(); + results += '\nDisconnected from XRP Ledger.'; + resultField.value = results; + } + } +} //End of getBatchNFTs() diff --git a/_code-samples/nft-modular-tutorials/broker-nfts.html b/_code-samples/nft-modular-tutorials/broker-nfts.html new file mode 100644 index 0000000000..9f923d6db8 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/broker-nfts.html @@ -0,0 +1,291 @@ + + + + Broker NFTs + + + + + + + + + + + +

Broker NFTs

+
+ + Choose your ledger instance: + +    + + +    + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +

+ +

+ + + + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + + +
+

+
+ + + + + + + +
+ + + + + + + +
+ + + + + +
+
+ +
+ + + + + +
+
+ +
+ + + + + +
+

+ +

+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/_code-samples/nft-modular-tutorials/broker-nfts.js b/_code-samples/nft-modular-tutorials/broker-nfts.js new file mode 100644 index 0000000000..7fa4932511 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/broker-nfts.js @@ -0,0 +1,44 @@ +// ******************************************************* +// ******************* Broker Sale *********************** +// ******************************************************* + +async function brokerSale() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + const net = getNet(); + const client = new xrpl.Client(net); + let results = `\n=== Connected. Brokering the sale. ===`; + resultField.value = results; + + try { + await client.connect(); + + // Prepare transaction ------------------------------------------------------- + const brokerTx = { + "TransactionType": "NFTokenAcceptOffer", + "Account": wallet.classicAddress, + "NFTokenSellOffer": nftSellOfferIndexField.value, + "NFTokenBuyOffer": nftBuyOfferIndexField.value, + "NFTokenBrokerFee": brokerFeeField.value + } + console.log(JSON.stringify(brokerTx, null, 2)); + // Submit transaction -------------------------------------------------------- + const tx = await client.submitAndWait(brokerTx, { wallet: wallet }) + + // Check transaction results ------------------------------------------------- + results += "\n\nTransaction result:\n" + + JSON.stringify(tx.result.meta.TransactionResult, null, 2) + results += "\nBalance changes:\n" + + JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2) + xrpBalanceField.value = (await client.getXrpBalance(wallet.address)) + resultField.value += results + } catch (error) { + console.error("Error in broker sale:", error); + results = `\n\n=== Error in broker sale: ${error.message} ===`; // User friendly + resultField.value += results; + } + finally { + if (client && client.isConnected()) { + await client.disconnect(); + } + } +}// End of brokerSale() \ No newline at end of file diff --git a/_code-samples/nft-modular-tutorials/mint-nfts.html b/_code-samples/nft-modular-tutorials/mint-nfts.html new file mode 100644 index 0000000000..d4c9bf7f0e --- /dev/null +++ b/_code-samples/nft-modular-tutorials/mint-nfts.html @@ -0,0 +1,313 @@ + + + Mint NFTs + + + + + + + + +

Mint NFTs

+
+ + Choose your ledger instance: + +    + + +    + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +

+ +

+ + + + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + + + + +    + +
+ + + +

+
+ + + +
+ + + + + + + +
+ + + + + +
+
+ +
+ + + + + +
+
+ + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + +
+

+ +

+
+
+ +
+
+ + + diff --git a/_code-samples/nft-modular-tutorials/mint-nfts.js b/_code-samples/nft-modular-tutorials/mint-nfts.js new file mode 100644 index 0000000000..99e330ab0e --- /dev/null +++ b/_code-samples/nft-modular-tutorials/mint-nfts.js @@ -0,0 +1,141 @@ +// ******************************************************* +// ********************** Mint NFT *********************** +// ******************************************************* + +async function mintNFT() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + const net = getNet(); + const client = new xrpl.Client(net); + let results = `\n=== Connected. Minting NFT ===`; + resultField.value = results; + + try { + await client.connect(); + + // Prepare transaction parameters + const transactionParams = { + TransactionType: "NFTokenMint", + Account: wallet.classicAddress, + URI: xrpl.convertStringToHex(nftURLfield.value), + Flags: parseInt(flagsField.value, 10), // Parse to integer + TransferFee: parseInt(transferFeeField.value, 10), // Parse to integer + NFTokenTaxon: parseInt(nftTaxonField.value, 10), // Parse to integer + }; + + // Add optional fields + if (amountField.value) { + transactionParams.Amount = configureAmount(amountField.value); + } + + if (expirationField.value) { + transactionParams.Expiration = configureExpiration(expirationField.value); + } + + if (destinationField.value) { + transactionParams.Destination = destinationField.value; + } + + console.log("Mint NFT Transaction Parameters:", transactionParams); // Log before submitting + + // Submit transaction + const tx = await client.submitAndWait(transactionParams, { wallet }); + + // Get minted NFTs + const nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress, + }); + + // Report results + results = `\n\n=== Transaction result: ${tx.result.meta.TransactionResult} ===`; + results += `\n\n=== NFTs: ${JSON.stringify(nfts, null, 2)} ===`; + results += `\n\n=== XRP Balance: ${await client.getXrpBalance(wallet.address)} ===`; // Await here + resultField.value = results; + + } catch (error) { + console.error("Error minting NFT:", error); + results += `\n\n=== Error minting NFT: ${error.message} ===`; // Use error.message + resultField.value = results; + } finally { + if (client && client.isConnected()) { // Check if connected before disconnecting + await client.disconnect(); + } + } +} // End of mintToken() + +// ******************************************************* +// ******************** Get NFTs ************************* +// ******************************************************* + +async function getNFTs() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + const net = getNet(); + const client = new xrpl.Client(net); + let results = '\n=== Connected. Getting NFTs. ==='; + resultField.value = results; + try { + await client.connect(); + + const nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress, + }); + results = '\n=== NFTs:\n ' + JSON.stringify(nfts, null, 2) + ' ==='; // Consistent formatting + resultField.value = results; + } catch (error) { + console.error("Error getting NFTs:", error); + results += `\n\n=== Error getting NFTs: ${error.message} ===`; // User-friendly + resultField.value = results; + } finally { + if (client && client.isConnected()) { + await client.disconnect(); + } + } +} // End of getNFTs() + +// ******************************************************* +// ********************** Burn NFT *********************** +// ******************************************************* + +async function burnNFT() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + const net = getNet(); + const client = new xrpl.Client(net); + let results = '\n=== Connected. Burning NFT. ==='; + resultField.value = results; + try { + await client.connect(); + + // Prepare transaction + const transactionBlob = { + TransactionType: "NFTokenBurn", + Account: wallet.classicAddress, + NFTokenID: nftIdField.value, + }; + + console.log("Burn NFT Transaction Parameters:", transactionBlob); // Log before submit + + // Submit transaction and wait for the results + const tx = await client.submitAndWait(transactionBlob, { wallet }); + const nfts = await client.request({ // Get nfts after burning. + method: "account_nfts", + account: wallet.classicAddress, + }); + + results = `\n=== Transaction result: ${tx.result.meta.TransactionResult} ===`; + results += '\n\n=== Balance changes: ' + + JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2) + ' ==='; + results += '\n\n=== NFTs: \n' + JSON.stringify(nfts, null, 2) + ' ==='; + resultField.value = results; + xrpBalanceField.value = (await client.getXrpBalance(wallet.address)); // Await + + } catch (error) { + console.error("Error burning NFT:", error); + results = `\n\n=== Error burning NFT: ${error.message} ===`; // User friendly + resultField.value = results; + } finally { + if (client && client.isConnected()) { + await client.disconnect(); + } + } +} // End of burnNFT() diff --git a/_code-samples/nft-modular-tutorials/modular-tutorials.css b/_code-samples/nft-modular-tutorials/modular-tutorials.css new file mode 100644 index 0000000000..1361142238 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/modular-tutorials.css @@ -0,0 +1,153 @@ +body { + font-family: "Inter", sans-serif; + padding: 20px; + background: #abe2ff; +} + +h1 { + font-weight: bold; +} + +td { + padding-left: 25px; + vertical-align: top; +} + +input, +button { + padding: 6px; + margin-bottom: 8px; + border: none +} + +input:read-only { + background-color:rgb(11, 96, 132); + color:white; + border: 0; +} + +button { + font-weight: bold; + font-family: "Work Sans", sans-serif; + background-color: #006aff; + -webkit-text-fill-color: white; + width: 144px; +} + +button:hover { + background-color: #0555c5; + cursor: pointer; +} +label { + font-weight: bold; +} +td { + vertical-align: middle; +} + +/* The switch - the box around the slider */ +.switch { + position: relative; + display: inline-block; + width: 30px; + height: 16px; +} + +/* Hide default HTML checkbox */ +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 13px; + width: 13px; + left: 4px; + bottom: 2px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked+.slider { + background-color: #2196F3; +} + +input:focus+.slider { + box-shadow: 0 0 1px #2196F3; +} + +input:checked+.slider:before { + -webkit-transform: translateX(13px); + -ms-transform: translateX(13px); + transform: translateX(13px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 17px; +} + +.slider.round:before { + border-radius: 50%; +} +.tooltip { + position: relative; + border-bottom: 1px dotted black; +} + +.tooltip:before { + content: attr(tooltip-data); + position: absolute; + width: 250px; + background-color: #006aff; + color: #fff; + text-align: center; + padding: 15px; + line-height: 1.1; + border-radius: 5px; + z-index: 1; + opacity: 0; + transition: opacity .5s; + bottom: 125%; + left: 50%; + margin-left: -60px; + font-size: 0.70em; + visibility: hidden; +} + +.tooltip:after { + content: ""; + position: absolute; + bottom: 75%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + opacity: 0; + transition: opacity .5s; + border-color: #000 transparent transparent transparent; + visibility: hidden; +} + +.tooltip:hover:before, +.tooltip:hover:after { + opacity: 1; + visibility: visible; +} \ No newline at end of file diff --git a/_code-samples/nft-modular-tutorials/nft-modular-tutorials.zip b/_code-samples/nft-modular-tutorials/nft-modular-tutorials.zip new file mode 100644 index 0000000000..789dc52e27 Binary files /dev/null and b/_code-samples/nft-modular-tutorials/nft-modular-tutorials.zip differ diff --git a/_code-samples/nft-modular-tutorials/transaction-support.js b/_code-samples/nft-modular-tutorials/transaction-support.js new file mode 100644 index 0000000000..7838dfa88b --- /dev/null +++ b/_code-samples/nft-modular-tutorials/transaction-support.js @@ -0,0 +1,36 @@ +// **************************************** +// ********* Configure Amount ************* +// **************************************** + +function configureAmount() { + let amount = ''; + if (currencyField.value === "XRP" || currencyField.value === "") { + if (amountField.value !== '') { + amount = amountField.value; // XRP amount should be a string of drops + } else { + amount = undefined; + } + } else if (currencyField.value !== "") { + amount = { + currency: currencyField.value, + issuer: issuerField.value, + value: amountField.value, + }; + } else { + amount = undefined; // Or handle the case where no currency is provided + } + return amount; +} + +// **************************************** +// ********* Configure Expiration ********* +// **************************************** + +function configureExpiration() { + let expiration = "" + var days = expirationField.value + let d = new Date() + d.setDate(d.getDate() + parseInt(days)) + expiration = xrpl.isoTimeToRippleTime(d) + return expiration +} // End of configureExpiration() diff --git a/_code-samples/nft-modular-tutorials/transfer-nfts.html b/_code-samples/nft-modular-tutorials/transfer-nfts.html new file mode 100644 index 0000000000..d1447a22b9 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/transfer-nfts.html @@ -0,0 +1,328 @@ + + + + Transfer NFTs + + + + + + + + + + + +

Transfer NFTs

+
+ + Choose your ledger instance: + +    + + +    + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +

+ +

+ + + + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + + +
+

+
+ + + + + +
+
+ +
+ + + + + +
+
+    + + +
+ + + + + +
+
+    + + +
+ + + + + +
+
+    + +
+ + + + + +
+ + + + + +
+ + + + + +
+ + + + + +
+

+ +

+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/_code-samples/nft-modular-tutorials/transfer-nfts.js b/_code-samples/nft-modular-tutorials/transfer-nfts.js new file mode 100644 index 0000000000..0ad7993186 --- /dev/null +++ b/_code-samples/nft-modular-tutorials/transfer-nfts.js @@ -0,0 +1,324 @@ + +// ********************************************************* +// *************** Create Sell Offer *********************** +// ********************************************************* + +async function createSellOffer() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + let results = '\nCreating sell offer...'; + resultField.value = results; + + try { + const client = new xrpl.Client(getNet()); + await client.connect(); + try { + const destination = destinationField.value || undefined; + const expiration = expirationField.value ? configureExpiration() : undefined; + + const transactionJson = { + TransactionType: "NFTokenCreateOffer", + Account: wallet.classicAddress, + NFTokenID: nftIdField.value, + Flags: 1, + }; + + const amount = configureAmount(); + if (amount) { // Only add Amount if it's defined + transactionJson.Amount = amount; + } else { + console.warn("Amount is undefined. Sell offer might be invalid."); + results += "\nWarning: Amount is undefined. Sell offer might be invalid, unless you plan to give away the NFT."; + resultField.value = results; + } + + if (expiration) { + transactionJson.Expiration = expiration; + } + if (destination) { + transactionJson.Destination = destination; + } + + console.log("Creating Sell Offer Transaction:", JSON.stringify(transactionJson, null, 2)); + + const tx = await client.submitAndWait(transactionJson, { wallet }); + results += `\nSell offer created successfully!\nTransaction Hash: ${tx.result.hash}\nEngine Result: ${tx.result.engine_result}`; + resultField.value = results; + + } finally { + client.disconnect(); + } + } catch (error) { + console.error("Error creating sell offer:", error); + results = `\nError: ${error.message || error}`; + resultField.value = results; + } +}// End of createSellOffer() + +// ******************************************************* +// ***************** Create Buy Offer ******************** +// ******************************************************* + +async function createBuyOffer() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + let net = getNet(); + const client = new xrpl.Client(net); + await client.connect(); + let results = '\n=== Connected. Creating buy offer. ==='; + resultField.value = results; + + try { + // Use the external configureAmount() function + let amount = configureAmount(); +console.log("Amount:", amount); + // Use the external configureExpiration() function + let expiration = configureExpiration(); // This will return a number or an empty string from the original logic + + let transactionJson = { + "TransactionType": "NFTokenCreateOffer", + "Account": wallet.classicAddress, + "Owner": nftOwnerField.value, + "NFTokenID": nftIdField.value, + "Flags": 0, // Ensure no tfSellNFToken flag for a buy offer + }; + + // Only add Amount if it's defined (not undefined or an empty string) + if (amount !== undefined && amount !== '') { + transactionJson.Amount = amount; + } else { + results += "\nError: Amount field is required for a buy offer."; + resultField.value = results; + client.disconnect(); + return; + } + + if (destinationField.value !== '') { + transactionJson.Destination = destinationField.value; + } + + // Only add Expiration if it's not an empty string + if (expiration > 0) { + transactionJson.Expiration = expiration; + } + + console.log("Buy Offer Transaction JSON:\n" + JSON.stringify(transactionJson, null, 2)); + + const tx = await client.submitAndWait(transactionJson, { wallet: wallet }); + + results += "\n\n=== Sell Offers ===\n"; + let nftSellOffers; + try { + nftSellOffers = await client.request({ + method: "nft_sell_offers", + nft_id: nftIdField.value + }); + } catch (err) { + nftSellOffers = "=== No sell offers. ==="; + } + results += JSON.stringify(nftSellOffers, null, 2); + results += "\n\n=== Buy Offers ===\n"; + let nftBuyOffers; + try { + nftBuyOffers = await client.request({ + method: "nft_buy_offers", + nft_id: nftIdField.value + }); + results += JSON.stringify(nftBuyOffers, null, 2); + } catch (err) { + results += "=== No buy offers. ==="; + } + + // Check transaction results ------------------------------------------------- + results += "\n\n=== Transaction result:\n" + + JSON.stringify(tx.result.meta.TransactionResult, null, 2); + results += "\n\n=== Balance changes:\n" + + JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2); + resultField.value = results; + + } catch (error) { + console.error('Error creating buy offer:', error); + results += "\n\n=== Error: " + error; + resultField.value = results; + } finally { + client.disconnect(); + } +}// End of createBuyOffer() + +// ******************************************************* +// ******************** Cancel Offer ********************* +// ******************************************************* + +async function cancelOffer() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value) + let net = getNet() + const client = new xrpl.Client(net) + await client.connect() + let results = "\n=== Connected. Cancelling offer. ===" + resultField.value = results + + const tokenOfferIDs = [nftOfferIdField.value] + + // Prepare transaction ------------------------------------------------------- + const transactionJson = { + "TransactionType": "NFTokenCancelOffer", + "Account": wallet.classicAddress, + "NFTokenOffers": tokenOfferIDs + } + // Submit transaction -------------------------------------------------------- + const tx = await client.submitAndWait(transactionJson, { wallet }) + + results += "\n\n=== Sell Offers===\n" + let nftSellOffers + try { + nftSellOffers = await client.request({ + method: "nft_sell_offers", + nft_id: nftIdField.value + }) + } catch (err) { + nftSellOffers = "=== No sell offers. ===" + } + results += JSON.stringify(nftSellOffers, null, 2) + results += "\n\n=== Buy Offers ===\n" + let nftBuyOffers + try { + nftBuyOffers = await client.request({ + method: "nft_buy_offers", + nft_id: nftIdField.value + }) + results += JSON.stringify(nftBuyOffers, null, 2) + } catch (err) { + nftBuyOffers = '=== No buy offers. ===' + } + + // Check transaction results ------------------------------------------------- + + results += "\n=== Transaction result:\n" + + JSON.stringify(tx.result.meta.TransactionResult, null, 2) + results += "\n=== Balance changes:\n" + + JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2) + resultField.value = results + + client.disconnect() // End of cancelOffer() +} + +// ******************************************************* +// ******************** Get Offers *********************** +// ******************************************************* + +async function getOffers() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value) + let net = getNet() + const client = new xrpl.Client(net) + await client.connect() + + let results = '\nConnected. Getting offers...' + resultField.value = results + + // --- Sell Offers --- + results += '\n\n=== Sell Offers ===\n' + let nftSellOffers + try { + nftSellOffers = await client.request({ + method: "nft_sell_offers", + nft_id: nftIdField.value + }) + } catch (err) { + nftSellOffers = 'No sell offers found for this NFT ID.' + } + results += JSON.stringify(nftSellOffers, null, 2) + resultField.value = results + + // --- Buy Offers --- + results += '\n\n=== Buy Offers ===\n' + let nftBuyOffers + try { + nftBuyOffers = await client.request({ + method: "nft_buy_offers", + nft_id: nftIdField.value + }) + } catch (err) { + // Log the actual error for debugging + nftBuyOffers = 'No buy offers found for this NFT ID.' // More descriptive + } + results += JSON.stringify(nftBuyOffers, null, 2) // Append the JSON string + resultField.value = results // Update the display with buy offers + + client.disconnect() +}// End of getOffers() + +// ******************************************************* +// ****************** Accept Sell Offer ****************** +// ******************************************************* + +async function acceptSellOffer() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value) + let net = getNet() + const client = new xrpl.Client(net) + await client.connect() + let results = '\n=== Connected. Accepting sell offer. ===\n\n' + resultField.value = results + + // Prepare transaction ------------------------------------------------------- + const transactionJson = { + "TransactionType": "NFTokenAcceptOffer", + "Account": wallet.classicAddress, + "NFTokenSellOffer": nftOfferIdField.value, + } + // Submit transaction -------------------------------------------------------- + const tx = await client.submitAndWait(transactionJson, { wallet: wallet }) + const nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress + }) + + // Check transaction results ------------------------------------------------- + + xrpBalanceField.value = (await client.getXrpBalance(wallet.address)) + + + results += '=== Transaction result:\n' + results += JSON.stringify(tx.result.meta.TransactionResult, null, 2) + results += '\n=== Balance changes:' + results += JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2) + results += JSON.stringify(nfts, null, 2) + resultField.value = results + + client.disconnect() +}// End of acceptSellOffer() + +// ******************************************************* +// ******************* Accept Buy Offer ****************** +// ******************************************************* + +async function acceptBuyOffer() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value) + let net = getNet() + const client = new xrpl.Client(net) + await client.connect() + let results = '\n=== Connected. Accepting buy offer. ===' + resultField.value = results + + // Prepare transaction ------------------------------------------------------- + const transactionJson = { + "TransactionType": "NFTokenAcceptOffer", + "Account": wallet.classicAddress, + "NFTokenBuyOffer": nftOfferIdField.value + } + // Submit transaction -------------------------------------------------------- + const tx = await client.submitAndWait(transactionJson, { wallet: wallet }) + const nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress + }) + results += JSON.stringify(nfts, null, 2) + resultField.value = results + + // Check transaction results ------------------------------------------------- + results += "\n\nTransaction result:\n" + + JSON.stringify(tx.result.meta.TransactionResult, null, 2) + results += "\nBalance changes:\n" + + JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2) + xrpBalanceField.value = + (await client.getXrpBalance(wallet.address)) + resultField.value = results + client.disconnect() +}// End of acceptBuyOffer() diff --git a/docs/img/mt-mint-token-1-empty-form.png b/docs/img/mt-mint-token-1-empty-form.png new file mode 100644 index 0000000000..fcbfd6374b Binary files /dev/null and b/docs/img/mt-mint-token-1-empty-form.png differ diff --git a/docs/img/mt-mint-token-2-accounts.png b/docs/img/mt-mint-token-2-accounts.png new file mode 100644 index 0000000000..82277489e9 Binary files /dev/null and b/docs/img/mt-mint-token-2-accounts.png differ diff --git a/docs/img/mt-mint-token-3-mint-token.png b/docs/img/mt-mint-token-3-mint-token.png new file mode 100644 index 0000000000..ec1ad1849d Binary files /dev/null and b/docs/img/mt-mint-token-3-mint-token.png differ diff --git a/docs/img/mt-mint-token-4-get-tokens.png b/docs/img/mt-mint-token-4-get-tokens.png new file mode 100644 index 0000000000..c687b691bf Binary files /dev/null and b/docs/img/mt-mint-token-4-get-tokens.png differ diff --git a/docs/img/mt-mint-token-5-burn-token.png b/docs/img/mt-mint-token-5-burn-token.png new file mode 100644 index 0000000000..08139f4dcc Binary files /dev/null and b/docs/img/mt-mint-token-5-burn-token.png differ diff --git a/docs/img/mt-transfer-nft-1-empty-form.png b/docs/img/mt-transfer-nft-1-empty-form.png new file mode 100644 index 0000000000..81fee658a4 Binary files /dev/null and b/docs/img/mt-transfer-nft-1-empty-form.png differ diff --git a/docs/img/mt-transfer-nft-2-accounts-loaded.png b/docs/img/mt-transfer-nft-2-accounts-loaded.png new file mode 100644 index 0000000000..73a7bbe120 Binary files /dev/null and b/docs/img/mt-transfer-nft-2-accounts-loaded.png differ diff --git a/docs/img/mt-transfer-nfts-3-create-sell-offer.png b/docs/img/mt-transfer-nfts-3-create-sell-offer.png new file mode 100644 index 0000000000..211c7c431a Binary files /dev/null and b/docs/img/mt-transfer-nfts-3-create-sell-offer.png differ diff --git a/docs/img/mt-transfer-nfts-4-get-offers.png b/docs/img/mt-transfer-nfts-4-get-offers.png new file mode 100644 index 0000000000..6481ed0c2a Binary files /dev/null and b/docs/img/mt-transfer-nfts-4-get-offers.png differ diff --git a/docs/img/mt-transfer-nfts-5-accept-sell-offer.png b/docs/img/mt-transfer-nfts-5-accept-sell-offer.png new file mode 100644 index 0000000000..ee507db92b Binary files /dev/null and b/docs/img/mt-transfer-nfts-5-accept-sell-offer.png differ diff --git a/docs/tutorials/javascript/nfts/mint-and-burn-nfts.md b/docs/tutorials/javascript/nfts/mint-and-burn-nfts.md index 3272bacd09..7a3d9baf08 100644 --- a/docs/tutorials/javascript/nfts/mint-and-burn-nfts.md +++ b/docs/tutorials/javascript/nfts/mint-and-burn-nfts.md @@ -1,10 +1,7 @@ --- -html: mint-and-burn-nfts-using-javascript.html -parent: nfts-using-javascript.html seo: description: Mint and burn NFTs. labels: - - Quickstart - Tokens - Non-fungible tokens, NFTs --- @@ -16,46 +13,47 @@ This example shows how to: 2. Get a list of existing NFTs. 3. Delete (Burn) an NFT. -[![Test harness with mint NFT fields](/docs/img/quickstart8.png)](/docs/img/quickstart8.png) +[![Mint NFTs Tester](../../../img/mt-mint-token-1-empty-form.png)](../../../img/mt-mint-token-1-empty-form.png) # Usage -You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/js/) archive to try the sample in your own browser. +You can download the [NFT Modular Tutorials](../../../../_code-samples/nft-modular-tutorials/nft-modular-tutorials.zip) archive to try the sample in your own browser. ## Get Accounts -1. Open `3.mint-nfts.html` in a browser. -2. Get test accounts. - 1. If you have existing Testnet account seeds: - 1. Paste the account seeds in the **Seeds** field. - 2. Click **Get Accounts from Seeds**. - 2. If you do not have existing Testnet accounts: - 1. Click **Get New Standby Account**. - 2. Click **Get New Operational Account**. +1. Open `mint-nfts.html` in a browser. +2. Choose your preferred test network (**Devnet** or **Testnet**). +3. Get test accounts. + 1. If you copied the gathered information from another tutorial: + 1. Paste the gathered information to the **Result** field. + 2. Click **Distribute Account Info**. + 2. If you have an existing account seed: + 1. Paste the account seed to the **Account 1 Seed** or **Account 2 Seed** field. + 2. Click **Get Account 1 from Seed** or **Get Account 2 from Seed**. + 2. If you do not have existing accounts: + 1. Click **Get New Account 1**. + 2. Click **Get New Account 2**. -[![Get accounts](/docs/img/quickstart9.png)](/docs/img/quickstart9.png) +[![Get accounts](../../../img/mt-mint-token-2-accounts.png)](../../../img/mt-mint-token-2-accounts.png) ## Mint an NFT -
- -
- To mint a non-fungible token object: 1. Set the **Flags** field. For testing purposes, we recommend setting the value to _8_. This sets the _tsTransferable_ flag, meaning that the NFT object can be transferred to another account. Otherwise, the NFT object can only be transferred back to the issuing account. -2. Enter the **Token URL**. This is a URI that points to the data or metadata associated with the NFT object. You can use the sample URI provided if you do not have one of your own. +2. Enter the **NFT URL**. This is a URI that points to the data or metadata associated with the NFT object. You can use the sample URI provided if you do not have one of your own. 3. Enter the **Transfer Fee**, a percentage of the proceeds from future sales of the NFT 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 the **Flags** field to allow the NFT to be transferrable, set this field to 0. If you impose a transfer fee, your NFT can only be traded for tokens for which your account has a trust line. See [Trust Lines](../../../concepts/tokens/fungible-tokens/index.md#trust-lines). -4. Click **Mint NFT**. +4. Optionally enter an **NFT Taxon**. This is a required value, but if you are not using the field to create an integer-based taxon entry, you can set the value to 0. +5. Click **Mint NFT**. -[![Mint NFT fields](/docs/img/quickstart10.png)](/docs/img/quickstart10.png) +[![Mint NFT fields](../../../img/mt-mint-token-3-mint-token.png)](../../../img/mt-mint-token-3-mint-token.png) ## Get Tokens Click **Get NFTs** to get a list of NFTs owned by the account. -[![Get NFTs](/docs/img/quickstart11.png)](/docs/img/quickstart11.png) +[![Get NFTs](../../../img/mt-mint-token-4-get-tokens.png)](../../../img/mt-mint-token-4-get-tokens.png) ## Burn a Token @@ -63,649 +61,549 @@ The current owner of an NFT can always destroy (or _burn_) an NFT object. To permanently destroy an NFT: -1. Enter the **Token ID**. +1. Enter or select the account that owns the NFT. +2. Enter the **NFT ID**. 2. Click **Burn NFT**. -[![Burn NFTs](/docs/img/quickstart12.png)](/docs/img/quickstart12.png) +[![Burn NFTs](../../../img/mt-mint-token-5-burn-token.png)](../../../img/mt-mint-token-5-burn-token.png) # Code Walkthrough -You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/js/) archive to examine the code samples. +You can download the [NFT Modular Tutorials](../../../../_code-samples/nft-modular-tutorials/nft-modular-tutorials.zip) archive to examine the code samples. -## ripplex3-mint-nfts.js +## mint-nfts.js -### Mint Token +### Mint NFT +Get the account wallet and connect to the XRP Ledger. ```javascript -// ******************************************************* -// ********************** Mint Token ********************* -// ******************************************************* - -async function mintToken() { +async function mintNFT() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + const net = getNet(); + const client = new xrpl.Client(net); + let results = `\n=== Connected. Minting NFT ===`; + resultField.value = results; + + try { + await client.connect(); ``` -Connect to the ledger and get the account wallets. +Prepare the transaction parameters. ```javascript - 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 NFT.' - standbyResultField.value = results + const transactionParams = { + TransactionType: "NFTokenMint", + Account: wallet.classicAddress, + URI: xrpl.convertStringToHex(nftURLfield.value), + Flags: parseInt(flagsField.value, 10), // Parse to integer + TransferFee: parseInt(transferFeeField.value, 10), // Parse to integer + NFTokenTaxon: parseInt(nftTaxonField.value, 10), // Parse to integer + }; ``` -Define the transaction. +Add optional fields. ```javascript + // Add optional fields + if (amountField.value) { + transactionParams.Amount = configureAmount(amountField.value); + } - const transactionJson = { - "TransactionType": "NFTokenMint", - "Account": standby_wallet.classicAddress, + if (expirationField.value) { + transactionParams.Expiration = configureExpiration(expirationField.value); + } + + if (destinationField.value) { + transactionParams.Destination = destinationField.value; + } ``` -Note that the URI field expects a hexadecimal value rather than the literal URI string. You can use the `convertStringToHex` utility to transform the URI in real time. +Log the transaction parameters before submission. ```javascript - "URI": xrpl.convertStringToHex(standbyTokenUrlField.value), + console.log("Mint NFT Transaction Parameters:", transactionParams); ``` -If you want the NFT to be transferable to third parties, set the **Flags** field to _8_. - +Submit the transaction. ```javascript - "Flags": parseInt(standbyFlagsField.value), + + const tx = await client.submitAndWait(transactionParams, { wallet }); ``` -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. +Get the current list of NFTs owned by the account. ```javascript - "TransferFee": parseInt(standbyTransferFeeField.value), + const nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress, + }); ``` -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_. - +Report the results of the transaction. ```javascript - "NFTokenTaxon": 0 //Required, but if you have no use for it, set to zero. + results = `\n\n=== Transaction result: ${tx.result.meta.TransactionResult} ===`; + results += `\n\n=== NFTs: ${JSON.stringify(nfts, null, 2)} ===`; + results += `\n\n=== XRP Balance: ${await client.getXrpBalance(wallet.address)} ===`; // Await here + resultField.value = results; +``` + +Catch and report any errors. + +```javascript + } catch (error) { + console.error("Error minting NFT:", error); + results += `\n\n=== Error minting NFT: ${error.message} ===`; // Use error.message + resultField.value = results; +``` + +Disconnect from the XRP Ledger. + +```javascript + } finally { + if (client && client.isConnected()) { // Check if connected before disconnecting + await client.disconnect(); + } } +} // End of mintToken() ``` +### Get NFTs -Send the transaction and wait for the response. +Get the wallet and connect to the XRP Ledger. ```javascript - const tx = await client.submitAndWait(transactionJson, { wallet: standby_wallet} ) +async function getNFTs() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + const net = getNet(); + const client = new xrpl.Client(net); + let results = '\n=== Connected. Getting NFTs. ==='; + resultField.value = results; + try { + await client.connect(); ``` -Request a list of NFTs owned by the account. +Prepare and send the `account_nfts` request. ```javascript - const nfts = await client.request({ - method: "account_nfts", - account: standby_wallet.classicAddress - }) + + const nfts = await client.request({ + method: "account_nfts", + account: wallet.classicAddress, + }); ``` Report the results. ```javascript - results += '\n\nTransaction result: '+ tx.result.meta.TransactionResult - results += '\n\nnfts: ' + JSON.stringify(nfts, null, 2) - standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address)) - standbyResultField.value = results + results = '\n=== NFTs:\n ' + JSON.stringify(nfts, null, 2) + ' ==='; + resultField.value = results; ``` -Disconnect from the ledger. +Catch and report any errors. ```javascript - client.disconnect() -} //End of mintToken() + } catch (error) { + console.error("Error getting NFTs:", error); + results += `\n\n=== Error getting NFTs: ${error.message} ===`; + resultField.value = results; ``` - -### Get Tokens +Disconnect from the XRP Ledger. ```javascript -// ******************************************************* -// ******************* Get Tokens ************************ -// ******************************************************* - -async function getTokens() { -``` - -Connect to the ledger and get the account. - -```javascript - 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 NFTs...' - standbyResultField.value = results -``` - -Request a list of NFTs owned by the account. - -```javascript - const nfts = await client.request({ - method: "account_nfts", - account: standby_wallet.classicAddress - }) -``` - -Report the results. - -```javascript - results += '\nNFTs:\n ' + JSON.stringify(nfts,null,2) - standbyResultField.value = results -``` - -Disconnect from the ledger. - -```javascript - client.disconnect() -} //End of getTokens() -``` - -### Burn Token - -```javascript -// ******************************************************* -// ********************* Burn Token ********************** -// ******************************************************* - -async function burnToken() { -``` - -Connect to the ledger and get the account wallets. - -```javascript - 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 NFT...' - standbyResultField.value = results -``` - -Define the transaction. - -```javascript - const transactionBlob = { - "TransactionType": "NFTokenBurn", - "Account": standby_wallet.classicAddress, - "NFTokenID": standbyTokenIdField.value + } finally { + if (client && client.isConnected()) { + await client.disconnect(); + } } +} // End of getNFTs() +``` + +### Burn NFT + +Get the account wallet and connect to the XRP Ledger. + +```javascript +sync function burnNFT() { + const wallet = xrpl.Wallet.fromSeed(accountSeedField.value); + const net = getNet(); + const client = new xrpl.Client(net); + let results = '\n=== Connected. Burning NFT. ==='; + resultField.value = results; + try { + await client.connect(); +``` + +Prepare the `NFTokenBurn` transaction. + +```javascript + const transactionBlob = { + TransactionType: "NFTokenBurn", + Account: wallet.classicAddress, + NFTokenID: nftIdField.value, + }; + + console.log("Burn NFT Transaction Parameters:", transactionBlob); // Log before submit ``` Submit the transaction and wait for the results. ```javascript - const tx = await client.submitAndWait(transactionBlob,{wallet: standby_wallet}) + const tx = await client.submitAndWait(transactionBlob, { wallet }); + const nfts = await client.request({ // Get nfts after burning. + method: "account_nfts", + account: wallet.classicAddress, + }); ``` -Request a list of NFTs owned by the client. - -```javascript - const nfts = await client.request({ - method: "account_nfts", - account: standby_wallet.classicAddress - }) -``` - - Report the results. - ```javascript - 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() + results = `\n=== Transaction result: ${tx.result.meta.TransactionResult} ===`; + results += '\n\n=== Balance changes: ' + + JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2) + ' ==='; + results += '\n\n=== NFTs: \n' + JSON.stringify(nfts, null, 2) + ' ==='; + resultField.value = results; + xrpBalanceField.value = (await client.getXrpBalance(wallet.address)); // Await ``` -### Reciprocal Transactions - -These transactions are the same as the Standby account transactions, but for the Operational account. +Catch and report any errors. ```javascript -// ******************************************************* -// ************** 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 NFT.' - operationalResultField.value = results + } catch (error) { + console.error("Error burning NFT:", error); + results = `\n\n=== Error burning NFT: ${error.message} ===`; // User friendly + resultField.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 += '\n\nTransaction result: '+ tx.result.meta.TransactionResult - results += '\n\nnfts: ' + JSON.stringify(nfts, null, 2) - operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address)) - operationalResultField.value = results - client.disconnect() -} //End of oPmintToken - -// ******************************************************* -// ************** Operational Get Tokens ***************** -// ******************************************************* +Disconnect from the XRP Ledger. -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 NFTs...' - 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 NFT...' - operationalResultField.value = results - - // ------------------------------------------------------- Prepare transaction - const transactionBlob = { - "TransactionType": "NFTokenBurn", - "Account": operational_wallet.classicAddress, - "NFTokenID": operationalTokenIdField.value +```javascript + } finally { + if (client && client.isConnected()) { + await client.disconnect(); + } } - - //-------------------------------------------------------- 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() ``` -## 3.mint-nfts.html +## mint-nfts.html ```html - - Token Test Harness + + Mint NFTs - - - - - - - - - - - - - -

Token Test Harness

+ + + + + + + +

Mint NFTs

- Choose your ledger instance: -    - - -    - - -

- -
- -

- - - - - -
- - + + Choose your ledger instance: + +    + + +    + + +

+
+ + - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Standby Account - - -
-
- Public Key - - -
-
- Private Key - - -
-
- Seed - - -
-
- XRP Balance - - -
-
- Amount - - -
-
- Destination - - -
-
- - -
- Currency - - -
NFT URL -
Flags
NFT ID
Transfer Fee
-

- -

-
- - - - - - -
- -

- -
- -
- -

- -
- -
- -
+
-
- - - -
- - - - - - - - -
- -

- -
- -
- -

- -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Operational Account - - -
-
- Public Key - - -
-
- Private Key - - -
-
- Seed - - -
-
- XRP Balance - - -
-
- Amount - - -
-
- Destination - - -
-
- - - -
- Currency - - -
NFT URL -
Flags
NFT ID
Transfer Fee
-

- -

-
+
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +

+ +

+ + + + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + + + + +    + +
+ + + +

+
+ + + +
+ + + + + + + +
+ + + + + +
+
+ +
+ + + + + +
+
+ + + + + +
+ + + + + +
+
+ + + + + +
+ + + + + +
+

+ +

+
+
+ +
- + + ``` diff --git a/docs/tutorials/javascript/nfts/transfer-nfts.md b/docs/tutorials/javascript/nfts/transfer-nfts.md index 702803e18f..366bb4be79 100644 --- a/docs/tutorials/javascript/nfts/transfer-nfts.md +++ b/docs/tutorials/javascript/nfts/transfer-nfts.md @@ -1,10 +1,7 @@ --- -html: transfer-nfts-using-javascript.html -parent: nfts-using-javascript.html seo: description: Use a JavaScript test harness to send XRP, trade currencies, and mint and trade NFTs. labels: - - Quickstart - Tokens - Non-fungible Tokens, NFTs --- @@ -19,43 +16,52 @@ This example shows how to: 5. Get a list of offers for a particular NFT. 6. Cancel an offer. -[![Quickstart form with NFT transfer fields](/docs/img/quickstart13.png)](/docs/img/quickstart13.png) +[![Transfer NFTs Test Harness](../../../img/mt-transfer-nft-1-empty-form.png)](../../../img/mt-transfer-nft-1-empty-form.png) -You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/js/) archive to try each of the samples in your own browser. +You can download the [NFT Modular Tutorials](../../../../_code-samples/nft-modular-tutorials/nft-modular-tutorials.zip) archive to try each of the samples in your own browser. # Usage ## Get Accounts -1. Open `4.transfer-nfts.html` in a browser. -2. Choose your ledger instance (**Testnet** or **Devnet**). +1. Open `transfer-nfts.html` in a browser. +2. Choose your preferred test network (**Devnet** or **Testnet**). 3. Get test accounts. - 1. If you have existing test account seeds - 1. Paste account seeds in the **Seeds** field. - 2. Click **Get Accounts from Seeds**. - 2. If you do not have test account seeds: - 1. Click **Get New Standby Account**. - 2. Click **Get New Operational Account**. + 1. If you copied the gathered information from another tutorial: + 1. Paste the gathered information to the **Result** field. + 2. Click **Distribute Account Info**. + 2. If you have an existing account seed: + 1. Paste the account seed to the **Account 1 Seed** or **Account 2 Seed** field. + 2. Click **Get Account 1 from Seed** or **Get Account 2 from Seed**. + 2. If you do not have existing accounts: + 1. Click **Get New Account 1**. + 2. Click **Get New Account 2**. -[![Form with account information](/docs/img/quickstart14.png)](/docs/img/quickstart14.png) +[![Form with account information](../../../img/mt-transfer-nft-2-accounts-loaded.png)](../../../img/mt-transfer-nft-2-accounts-loaded.png) ## Create a Sell Offer -
- -
+To create an NFT sell offer: -To create a NFT sell offer: - -1. Enter the **Amount** of the sell offer in drops (millionths of an XRP). -2. Set the **Flags** field to _1_. -3. Enter the **NFT ID** of the NFT you want to sell. -4. Optionally, enter a number of days until **Expiration**. +1. Enter the **Amount** of the sell offer. + 1. If using _XRP_, enter the **Amount** in drops (millionths of an XRP; for example, 20000000). + 2. If using issued currency, enter the **Currency** code, **Issuer** account address, and the **Amount**. +2. Optionally, include a **Destination** account address. If present, only that account will have permission to accept the sell offer. +3. Optionally, enter a number of days until **Expiration** of the offer. +4. Enter the **NFT ID** of the NFT you want to sell. 5. Click **Create Sell Offer**. -The important piece of information in the response is the NFT Offer Index, labeled as `nft_offer_index`, which you use to accept the sell offer. +[![NFT Sell Offer](../../../img/mt-transfer-nfts-3-create-sell-offer.png)](../../../img/mt-transfer-nfts-3-create-sell-offer.png) -[![NFT Sell Offer](/docs/img/quickstart15.png)](/docs/img/quickstart15.png) +## Get Offers + +To list the buy and sell offers associated with an NFT: +1. Enter the **NFT ID**. +2. Click **Get Offers**. + +The key piece of information is the NFT Offer ID (labeled as `nft_offer_index`), which you use to accept a sell or buy offer. + +[![Get offers](../../../img/mt-transfer-nfts-4-get-offers.png)](../../../img/mt-transfer-nfts-4-get-offers.png) ## Accept Sell Offer @@ -66,39 +72,33 @@ To accept an available sell offer: 1. Enter the **NFT Offer Index** (labeled as `nft_offer_index` in the token offer results. This is different from the `NFTokenID`.) 2. Click **Accept Sell Offer**. -[![Accept Sell Offer](/docs/img/quickstart16.png)](/docs/img/quickstart16.png) +[![Accept Sell Offer](../../../img/mt-transfer-nfts-5-accept-sell-offer.png)](../../../img/mt-transfer-nfts-5-accept-sell-offer.png) ## Create a Buy Offer -You can offer to buy a NFT from another account. +You can offer to buy an NFT from another account. -To create an offer to buy a NFT: +To create an offer to buy an NFT: 1. Enter the **Amount** of your offer. -2. Enter the **NFT ID**. -3. Enter the owner’s account string in the **Owner** field. -4. Optionally enter the number of days until **Expiration**. + 1. If paying XRP, enter the **Amount** of XRP in drops (1000000 equals 1 XRP). + 2. If paying an issued currency, enter the **Currency**, **Issuer**, and **Amount**. +2. Optionally enter the number of days until **Expiration**. +3. Enter the **NFT ID**. +4. Enter the owner’s account address in the **NFT Owner Address** field. 5. Click **Create Buy Offer**. [![NFT Buy Offer](/docs/img/quickstart17.png)](/docs/img/quickstart17.png) ## Accept a Buy Offer -To accept an offer to buy a NFT: +To accept an offer to buy an NFT: 1. Enter the **NFT Offer Index** (the `nft_offer_index` of the NFT buy offer). 3. Click **Accept Buy Offer**. [![Accept Buy Offer](/docs/img/quickstart18.png)](/docs/img/quickstart18.png) -## Get Offers - -To list the buy and sell offers associated with a NFT: -1. Enter the **NFT ID**. -2. Click **Get Offers**. - -[![Get offers](/docs/img/quickstart19.png)](/docs/img/quickstart19.png) - ## Cancel Offer To cancel a buy or sell offer that you have created: diff --git a/docs/tutorials/javascript/send-payments/create-offers.md b/docs/tutorials/javascript/send-payments/create-offers.md index 9c9073fd2f..e6c97beb82 100644 --- a/docs/tutorials/javascript/send-payments/create-offers.md +++ b/docs/tutorials/javascript/send-payments/create-offers.md @@ -28,7 +28,8 @@ Download and expand the [Modular Tutorials](../../../../_code-samples/modular-tu To get test accounts: 1. Open `create-offers.html` in a browser. -2. Get test accounts. +2. Choose your preferred test network (**Devnet** or **Testnet**). +3. Get test accounts. 1. If you copied the gathered information from another tutorial: 1. Paste the gathered information to the **Result** field. 2. Click **Distribute Account Info**.