From fd273dccb815a4d3cf6874c2d10519cc0969e405 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Thu, 7 Sep 2023 16:41:16 -0700 Subject: [PATCH] Fix interactive tutorial faucet interface on Devnet; migrate AMM tutorial to Devnet --- assets/js/interactive-tutorial.js | 25 +- assets/js/tutorials/create-amm.js | 269 ++++++++++++++++++ .../_code-samples/create-amm/js/connect.js | 4 +- .../_code-samples/create-amm/js/create-amm.js | 22 +- .../create-an-automated-market-maker.md | 4 +- 5 files changed, 299 insertions(+), 25 deletions(-) create mode 100644 assets/js/tutorials/create-amm.js diff --git a/assets/js/interactive-tutorial.js b/assets/js/interactive-tutorial.js index 01d6a4e473..48dcf46d50 100644 --- a/assets/js/interactive-tutorial.js +++ b/assets/js/interactive-tutorial.js @@ -186,29 +186,36 @@ function setup_generate_step() { const faucet_url = $("#generate-creds-button").data("fauceturl") try { - // destination not defined - API will create account. - const data = await call_faucet(faucet_url, undefined, event) + const wallet = xrpl.Wallet.generate("ed25519") + const data = await call_faucet(faucet_url, wallet.address, event) block.find(".loader").hide() block.find(".output-area").html(`
${tl("Address:")} ${data.account.address}
${tl("Secret:")} - ${data.account.secret}
- ${tl("Balance:")} - ${Number(data.balance).toLocaleString(current_locale)} XRP`) + ${wallet.seed}`) + if (data.balance) { + block.find(".output-area").append(`
${tl("Balance:")} + ${Number(data.balance).toLocaleString(current_locale)} XRP
`) + } // Automatically populate all examples in the page with the // generated credentials... + let creds_updated = false; $("code span:contains('"+EXAMPLE_ADDR+"')").each( function() { + creds_updated = true let eltext = $(this).text() $(this).text( eltext.replace(EXAMPLE_ADDR, data.account.address) ) }) $("code span:contains('"+EXAMPLE_SECRET+"')").each( function() { + creds_updated = true let eltext = $(this).text() $(this).text( eltext.replace(EXAMPLE_SECRET, data.account.secret) ) }) - block.find(".output-area").append(`

${tl("Populated this page's examples with these credentials.")}

`) + if (creds_updated) { + block.find(".output-area").append(`

${tl("Populated this page's examples with these credentials.")}

`) + } complete_step("Generate") @@ -278,7 +285,7 @@ async function call_faucet(faucet_url, destination, event) { step: block.data("stepnumber"), totalsteps: block.data("totalsteps"), }; - //pass in plain text instead of HEX- the API will encode. + //pass in plain text instead of HEX- the API will encode. const memo = { data: JSON.stringify(tutorial_info, null, 0), format: "application/json", // application/json @@ -288,9 +295,7 @@ async function call_faucet(faucet_url, destination, event) { }; const body = {}; - if (typeof destination != "undefined") { - body["destination"] = destination; - } + body["destination"] = destination; body["memos"] = [memo]; const response = await fetch(faucet_url, { diff --git a/assets/js/tutorials/create-amm.js b/assets/js/tutorials/create-amm.js new file mode 100644 index 0000000000..df13add292 --- /dev/null +++ b/assets/js/tutorials/create-amm.js @@ -0,0 +1,269 @@ +// 1. Generate +// 2. Connect +// The code for these steps is handled by interactive-tutorial.js +$(document).ready(() => { + + const EXPLORER = $("#connect-button").data("explorer") + + $("#get-foo").click( async (event) => { + const block = $(event.target).closest(".interactive-block") + const wallet = get_wallet(event) + if (!wallet) {return} + + const currency_code = "FOO" + const issue_quantity = "1000" + + block.find(".loader").show() + show_log(block, "

Funding an issuer address with the faucet...

") + const issuer = (await api.fundWallet()).wallet + show_log(block, `

Got issuer ${issuer.address}.

`) + $(".foo-issuer").text(issuer.address) // Update display in the "Create AMM" step + + // Enable issuer DefaultRipple ---------------------------------------------- + const issuer_setup_tx = { + "TransactionType": "AccountSet", + "Account": issuer.address, + "SetFlag": xrpl.AccountSetAsfFlags.asfDefaultRipple + } + add_memo(event, issuer_setup_tx) + const issuer_setup_result = await api.submitAndWait(issuer_setup_tx, {autofill: true, wallet: issuer} ) + if (issuer_setup_result.result.meta.TransactionResult == "tesSUCCESS") { + show_log(block, `

✅ Issuer DefaultRipple enabled

`) + } else { + show_error(block, `Error sending transaction:
${pretty_print(issuer_setup_result)}
`) + } + + // Create trust line to issuer ---------------------------------------------- + const trust_tx = { + "TransactionType": "TrustSet", + "Account": wallet.address, + "LimitAmount": { + "currency": currency_code, + "issuer": issuer.address, + "value": "10000000000" // Large limit, arbitrarily chosen + } + } + add_memo(event, trust_tx) + const trust_result = await api.submitAndWait(trust_tx, {autofill: true, wallet: wallet}) + if (trust_result.result.meta.TransactionResult == "tesSUCCESS") { + show_log(block, `

✅ Trust line created

`) + } else { + show_error(block, `Error sending transaction:
${pretty_print(trust_result)}
`) + } + + // Issue tokens ------------------------------------------------------------- + const issue_tx = { + "TransactionType": "Payment", + "Account": issuer.address, + "Amount": { + "currency": currency_code, + "value": issue_quantity, + "issuer": issuer.address + }, + "Destination": wallet.address + } + add_memo(event, issue_tx) + const issue_result = await api.submitAndWait(issue_tx, {autofill: true, wallet: issuer}) + if (issue_result.result.meta.TransactionResult == "tesSUCCESS") { + show_log(block, `

✅ Tokens issued

`) + $("#get-foo").data("foo-acquired", true).prop("disabled", true).addClass("disabled").addClass("done") + } else { + show_error(block, `Error sending transaction:
${pretty_print(issue_result)}
`) + } + block.find(".loader").hide() + + if ($("#get-foo").data("foo-acquired") && $("#buy-tst").data("tst-acquired")) { + complete_step("Acquire tokens") + } + }) + + $("#buy-tst").click( async (event) => { + const block = $(event.target).closest(".interactive-block") + const wallet = get_wallet(event) + if (!wallet) {return} + block.find(".loader").show() + + const tx_json = { + "TransactionType": "OfferCreate", + "Account": wallet.address, + "TakerPays": { + currency: "TST", + issuer: "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd", + value: "25" + }, + "TakerGets": xrpl.xrpToDrops(25*10*1.16) + } + add_memo(event, tx_json) + + const offer_result = await api.submitAndWait(tx_json, {autofill: true, wallet: wallet}) + + if (offer_result.result.meta.TransactionResult == "tesSUCCESS") { + show_log(block, `

✅ TST offer placed

`) + const balance_changes = xrpl.getBalanceChanges(offer_result.result.meta) + for (const bc of balance_changes) { + if (bc.account != wallet.address) {continue} + for (const bal of bc.balances) { + if (bal.currency == "TST") { + show_log(block, `

Got ${bal.value} ${bal.currency}.${bal.issuer}.

`) + break + } + } + break + } + $("#buy-tst").data("tst-acquired", true).prop("disabled", true).addClass("disabled").addClass("done") + } else { + show_error(block, `

Transaction failed:

${pretty_print(offer_result)}
`) + } + block.find(".loader").hide() + + if ($("#get-foo").data("foo-acquired") && $("#buy-tst").data("tst-acquired")) { + complete_step("Acquire tokens") + } + }) + + $("#check-for-amm").click( async (event) => { + const block = $(event.target).closest(".interactive-block") + const foo_issuer_address = $("#issuer-address").text() + + block.find(".output-area").html("") + block.find(".loader").show() + try { + const amm_info = await api.request({ + "command": "amm_info", + "asset": { + "currency": "TST", + "issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" + }, + "asset2": { + "currency": "FOO", + "issuer": foo_issuer_address + }, + "ledger_index": "validated" + }) + show_log(block, `
${pretty_print}amm_info
`) + } catch(err) { + if (err.data.error === 'actNotFound') { + show_log(block, `

✅ No AMM exists yet for the pair + FOO.${foo_issuer_address} / + TST.rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd.`) + complete_step("Check for AMM") + } else { + show_error(block, err) + } + } + block.find(".loader").hide() + }) + + $("#look-up-ammcreate-cost").click( async (event) => { + const block = $(event.target).closest(".interactive-block") + block.find(".loader").show() + let amm_fee_drops = "5000000" + try { + const ss = await api.request({"command": "server_state"}) + amm_fee_drops = ss.result.state.validated_ledger.reserve_inc.toString() + show_log(block, `

Current AMMCreate transaction cost: ${xrpl.dropsToXrp(amm_fee_drops)} XRP (${amm_fee_drops} drops)

`) + complete_step("Look up AMMCreate cost") + } catch(err) { + show_error(block, `Error looking up AMMCreate tx cost: ${err}`) + } + block.find(".loader").hide() + }) + + $("#create-amm").click( async (event) => { + const block = $(event.target).closest(".interactive-block") + const wallet = get_wallet(event) + if (!wallet) {return} + + amm_fee_drops = $("#ammcreate-cost-drops").text() + if (!amm_fee_drops) {return} + + const asset_amount = $("#asset-amount").val() + const asset2_amount = $("#asset2-amount").val() + const asset2_issuer_address = $("#issuer-address").text() + const trading_fee = Math.floor($("#trading-fee").val()*1000) // Convert from % + + const ammcreate_tx = { + "TransactionType": "AMMCreate", + "Account": wallet.address, + "Amount": { + currency: "TST", + issuer: "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd", + value: asset_amount + }, + "Amount2": { + "currency": "FOO", + "issuer": asset2_issuer_address, + "value": asset2_amount + }, + "TradingFee": 500, // 0.5% + "Fee": amm_fee_drops + } + add_memo(event, ammcreate_tx) + const ammcreate_result = await api.submitAndWait(ammcreate_tx, {autofill: true, wallet: wallet, fail_hard: true}) + if (ammcreate_result.result.meta.TransactionResult == "tesSUCCESS") { + show_log(block, `

AMM created:

+
${pretty_print(ammcreate_result)}
`) + complete_step("Create AMM") + } else { + throw `Error sending transaction: ${ammcreate_result}` + } + block.find(".loader").hide() + }) + + $("#check-amm-info").click( async (event) => { + const block = $(event.target).closest(".interactive-block") + const foo_issuer_address = $("#issuer-address").text() + + block.find(".output-area").html("") + block.find(".loader").show() + try { + const amm_info = await api.request({ + "command": "amm_info", + "asset": { + "currency": "TST", + "issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd" + }, + "asset2": { + "currency": "FOO", + "issuer": foo_issuer_address + }, + "ledger_index": "validated" + }) + show_log(block, `

AMM Info:

${pretty_print(amm_info)}
`) + const lp_token = amm_info.result.amm.lp_token + show_log(block, `

The AMM account ${lp_token.issuer} has ${lp_token.value} total + LP tokens outstanding, and uses the currency code ${lp_token.currency}.

`) + const amount = amm_info.result.amm.amount + const amount2 = amm_info.result.amm.amount2 + show_log(block, `

In its pool, the AMM holds ${amount.value} ${amount.currency}.${amount.issuer} + and ${amount2.value} ${amount2.currency}.${amount2.issuer}

`) + complete_step("Check AMM info") + } catch(err) { + show_error(block, err) + } + block.find(".loader").hide() + }) + + $("#check-trust-lines").click( async (event) => { + const block = $(event.target).closest(".interactive-block") + const address = get_address() + if (!address) {return} + + block.find(".output-area").html("") + block.find(".loader").show() + try { + const account_lines = await api.request({ + "command": "account_lines", + "account": address, + "ledger_index": "validated" + }) + show_log(block, `

Trust lines:

${pretty_print(account_lines)}
`) + complete_step("Check trust lines") + } catch(err) { + show_error(block, err) + } + block.find(".loader").hide() + }) + +}) + diff --git a/content/_code-samples/create-amm/js/connect.js b/content/_code-samples/create-amm/js/connect.js index 5ee463ea25..483ebb0cbf 100644 --- a/content/_code-samples/create-amm/js/connect.js +++ b/content/_code-samples/create-amm/js/connect.js @@ -1,8 +1,8 @@ // In browsers, use a