From 6866ce295bf66550930fa0a24b7c7bc457216d7e Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 17 Apr 2019 18:43:39 -0700 Subject: [PATCH 1/6] Tx Sender: UI, connectivity --- assets/js/tx-sender.js | 55 +++++++++++++++ dactyl-config.yml | 7 ++ tool/template-tx-sender.html | 127 +++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 assets/js/tx-sender.js create mode 100644 tool/template-tx-sender.html diff --git a/assets/js/tx-sender.js b/assets/js/tx-sender.js new file mode 100644 index 0000000000..87af2b8c49 --- /dev/null +++ b/assets/js/tx-sender.js @@ -0,0 +1,55 @@ +const set_up_tx_sender = async function() { + FAUCET_URL = "https://faucet.altnet.rippletest.net/accounts" + TESTNET_URL = "wss://s.altnet.rippletest.net:51233" + + let sending_address = "" + let sending_secret = "" + let xrp_balance = 0 + + console.debug("Getting a sending address from the faucet...") + + faucet_response = function(data) { + sending_address = data.account.address + sending_secret = data.account.secret + xrp_balance = Number(data.balance) // Faucet only delivers ~10,000 XRP, + // so this won't go over JavaScript's + // 64-bit double precision + + $("#balance-item").text(xrp_balance) + " drops" + $(".sending-address-item").text(sending_address) + } + + // TEMP: reuse same address for testing + faucet_response({account:{address:"r6f2viHtMjNSfERbZmXXkJnMmkBAN6d9X", secret:"spyTc4y4GAQwBfQHCxiJ1Xd2mmnM2"},balance:10000}) + + // POST-TEMP: Version that actually uses the faucet on every run: + // $.ajax({ + // url: FAUCET_URL, + // type: 'POST', + // dataType: 'json', + // success: faucet_response, + // error: function() { + // alert("There was an error with the XRP Ledger Test Net Faucet. Reload this page to try again."); + // } + // }) + // + + api = new ripple.RippleAPI({server: TESTNET_URL}) + api.on('connected', () => { + $("#connection-status-item").text("Connected") + $("#connection-status-item").removeClass("disabled").addClass("active") + }) + api.on('disconnected', (code) => { + $("#connection-status-item").text("Not connected") + $("#connection-status-item").removeClass("active").addClass("disabled") + }) + console.log("Connecting to Test Net WebSocket...") + api.connect() + + +} + +$(document).ready( function() { + console.log("doc ready!") + set_up_tx_sender() +} ) diff --git a/dactyl-config.yml b/dactyl-config.yml index f9110a1e8a..db9b98b630 100644 --- a/dactyl-config.yml +++ b/dactyl-config.yml @@ -2915,6 +2915,13 @@ pages: - local template: template-test-net.html + - name: Transaction Sender + funnel: Dev Tools + html: tx-sender.html + targets: + - local + template: template-tx-sender.html + # News ------------------------------------------------------------------------- - md: news/news.md diff --git a/tool/template-tx-sender.html b/tool/template-tx-sender.html new file mode 100644 index 0000000000..15f7b199b0 --- /dev/null +++ b/tool/template-tx-sender.html @@ -0,0 +1,127 @@ +{% extends "template-base.html" %} + +{% block bodyclasses %}page-tx-sender{% endblock %} + +{% block right_sidebar %} +
+
+

Status

+
+
+
    +
  • XRP Test Net:
  • +
  • Not Connected
  • +
  • Sending Address:
  • +
  • (None)
  • +
  • Test XRP Available:
  • +
  • (None)
  • +
+
+
+{% endblock %} + +{% block main %} +
+

Transaction Sender

+ +
+

This tool sends transactions to the XRP Test Net address of your choice so you can test how you monitor and respond to incoming transactions.

+ +
+
+ + + Send transactions to this XRP Test Net address +
+ +

Send Transaction

+ +
+
+ +
+ + drops of XRP +
+
+ Send a simple XRP-to-XRP payment. +
+ +
+ +
+
+ +
+ Delivers a small amount of XRP with a large Amount value, to test your handling of partial payments. +
+ +
+ +
+
+ +
+ for + + seconds + + ( + + ) + +
+
+ Create a time-based escrow of 1 XRP. +
+ +
+ +
+
+ +
+ funded with + + drops of XRP +
+
+ Create a payment channel. +
+ +
+ +
+
+ +
+ + FOO +
+
+ Your destination address should trust (sending address) for the currency in question. +
+ +
+ +
+
+ +
+ + FOO +
+
+ The test sender creates a trust line to your account for the given currency. +
+ +
+
+
+{% endblock %} + +{% block endbody %} + + + +{% endblock %} From 668fbe2fbae35f907a5a30a6d208539cfecd7d5b Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Mon, 22 Apr 2019 19:37:42 -0700 Subject: [PATCH 2/6] Tx Sender: Implement all types except partial payment --- assets/css/devportal.css | 1 + assets/js/tx-sender.js | 224 ++++++++++++++++++++++++++++++++++- tool/template-tx-sender.html | 70 +++++++---- 3 files changed, 268 insertions(+), 27 deletions(-) diff --git a/assets/css/devportal.css b/assets/css/devportal.css index 2330fed6d4..489efdebb4 100644 --- a/assets/css/devportal.css +++ b/assets/css/devportal.css @@ -1137,6 +1137,7 @@ a.current { } .page-test-net .throbber, +.page-tx-sender .throbber, .interactive-block .throbber { -webkit-animation: rotating 1s linear infinite; -moz-animation: rotating 1s linear infinite; diff --git a/assets/js/tx-sender.js b/assets/js/tx-sender.js index 87af2b8c49..cb4f48dbb2 100644 --- a/assets/js/tx-sender.js +++ b/assets/js/tx-sender.js @@ -1,4 +1,8 @@ const set_up_tx_sender = async function() { + ////////////////////////////////////////////////////////////////////////////// + // Connection / Setup + ////////////////////////////////////////////////////////////////////////////// + FAUCET_URL = "https://faucet.altnet.rippletest.net/accounts" TESTNET_URL = "wss://s.altnet.rippletest.net:51233" @@ -46,10 +50,228 @@ const set_up_tx_sender = async function() { console.log("Connecting to Test Net WebSocket...") api.connect() + ////////////////////////////////////////////////////////////////////////////// + // Generic Transaction Submission + ////////////////////////////////////////////////////////////////////////////// + + const INTERVAL = 1000 // milliseconds to wait for new ledger versions + async function verify_transaction(hash, options) { + try { + data = await api.getTransaction(hash, options) + return data + } catch(error) { + /* If transaction not in latest validated ledger, + try again until max ledger hit */ + if (error instanceof api.errors.PendingLedgerVersionError) { + return new Promise((resolve, reject) => { + setTimeout(() => verify_transaction(hash, options) + .then(resolve, reject), INTERVAL) + }) + } else { + throw error; + } + } + } + + async function submit_and_verify(tx_object) { + try { + // Auto-fill fields like Fee and Sequence + prepared = await api.prepareTransaction(tx_object) + } catch(error) { + console.log(error) + alert("Error preparing tx: "+error) + return + } + + // Determine first and last ledger the tx could be validated in *BEFORE* + // signing it. + const options = { + minLedgerVersion: (await api.getLedger()).ledgerVersion, + maxLedgerVersion: prepared.instructions.maxLedgerVersion + } + + try { + // Sign, submit + sign_response = api.sign(prepared.txJSON, sending_secret) + await api.submit(sign_response.signedTransaction) + } catch (error) { + console.log(error) + alert("Error signing & submitting tx: "+error) + return + } + + // Wait for tx to be in a validated ledger or to expire + try { + const data = await verify_transaction(sign_response.id, options) + const final_result = data.outcome.result + // TODO: more "notification-like" system + // TODO: output should link to a tx lookup/explainer + if (final_result === "tesSUCCESS") { + alert("Tx succeeded (hash:"+sign_response.id+")") + } else { + alert("Tx failed w/ code "+final_result+" (hash: "+sign_response.id+")") + } + return data + } catch(error) { + alert("Error submitting tx: "+error) + } + + } + + + ////////////////////////////////////////////////////////////////////////////// + // Button Handlers + ////////////////////////////////////////////////////////////////////////////// + + // 1. Send XRP Payment Handler ------------------------------------------- + async function on_click_send_xrp_payment(event) { + const destination_address = $("#destination_address").val() + const xrp_drops_input = $("#send_xrp_payment_amount").val() + $("#send_xrp_payment .loader").show() + $("#send_xrp_payment button").attr("disabled","disabled") + await submit_and_verify({ + TransactionType: "Payment", + Account: sending_address, + Destination: destination_address, + Amount: xrp_drops_input + }) + $("#send_xrp_payment .loader").hide() + $("#send_xrp_payment button").attr("disabled",false) + + } + $("#send_xrp_payment button").click(on_click_send_xrp_payment) + + // 2. Send Partial Payment Handler --------------------------------------- + // TODO + + // 3. Create Escrow Handler ---------------------------------------------- + async function on_click_create_escrow(event) { + const destination_address = $("#destination_address").val() + const duration_seconds_txt = $("#create_escrow_duration_seconds").val() + const release_auto = $("#create_escrow_release_automatically").prop("checked") + + const duration_seconds = parseInt(duration_seconds_txt, 10) + if (duration_seconds === NaN || duration_seconds < 1) { + alert("Error: Escrow duration must be a positive number of seconds") + return + } + const finish_after = api.iso8601ToRippleTime(Date()) + duration_seconds + + $("#create_escrow .loader").show() + $("#create_escrow button").attr("disabled","disabled") + const escrowcreate_tx_data = await submit_and_verify({ + TransactionType: "EscrowCreate", + Account: sending_address, + Destination: destination_address, + Amount: "1000000", + FinishAfter: finish_after + }) + + if (release_auto) { + // Wait until there's a ledger with a close time > FinishAfter + // Check at the FinishAfter time, then approx. every INTERVAL thereafter + function timeout(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + let delay_ms = (finish_after - api.iso8601ToRippleTime(Date())) * 1000 + let latestCloseTimeRipple = null + while (delay_ms > 0) { + // console.log("Waiting another "+delay_ms+"ms for escrow to be ready") + await timeout(delay_ms) + latestCloseTimeRipple = api.iso8601ToRippleTime((await api.getLedger()).closeTime) + if (latestCloseTimeRipple > finish_after) { + delay_ms = 0 + } else { + delay_ms = 1000 + } + } + + // Now submit the EscrowFinish + // Future feature: submit from a different sender, just to prove that + // escrows can be finished by a third party + await submit_and_verify({ + Account: sending_address, + TransactionType: "EscrowFinish", + Owner: sending_address, + OfferSequence: escrowcreate_tx_data.sequence + }) + } + $("#create_escrow .loader").hide() + $("#create_escrow button").attr("disabled",false) + } + $("#create_escrow button").click(on_click_create_escrow) + + // 4. Create Payment Channel Handler ------------------------------------- + async function on_click_create_payment_channel(event) { + const destination_address = $("#destination_address").val() + const xrp_drops_input = $("#create_payment_channel_amount").val() + const pubkey = api.deriveKeypair(sending_secret).publicKey + $("#create_payment_channel .loader").show() + $("#create_payment_channel button").attr("disabled","disabled") + await submit_and_verify({ + TransactionType: "PaymentChannelCreate", + Account: sending_address, + Destination: destination_address, + Amount: xrp_drops_input, + SettleDelay: 30, + PublicKey: pubkey + }) + $("#create_payment_channel .loader").hide() + $("#create_payment_channel button").attr("disabled",false) + + // Future feature: figure out channel ID and enable a button that creates + // valid claims for the given payment channel to help test redeeming + } + $("#create_payment_channel button").click(on_click_create_payment_channel) + + + // 5. Send Issued Currency Handler --------------------------------------- + async function on_click_send_issued_currency(event) { + const destination_address = $("#destination_address").val() + const issue_amount = $("#send_issued_currency_amount").val() + const issue_code = $("#send_issued_currency_code").text() + $("#send_issued_currency .loader").show() + $("#send_issued_currency button").attr("disabled","disabled") + // Future feature: cross-currency sending with paths? + await submit_and_verify({ + TransactionType: "Payment", + Account: sending_address, + Destination: destination_address, + Amount: { + "currency": issue_code, + "value": issue_amount, + "issuer": sending_address + } + }) + $("#send_issued_currency .loader").hide() + $("#send_issued_currency button").attr("disabled",false) + } + $("#send_issued_currency button").click(on_click_send_issued_currency) + + // 6. Trust For Handler + async function on_trust_for(event) { + const destination_address = $("#destination_address").val() + const trust_limit = $("#trust_for_amount").val() + const trust_currency_code = $("#trust_for_currency_code").text() + $("#trust_for .loader").show() + $("#trust_for button").attr("disabled","disabled") + await submit_and_verify({ + TransactionType: "TrustSet", + Account: sending_address, + LimitAmount: { + currency: trust_currency_code, + value: trust_limit, + issuer: destination_address + } + }) + $("#trust_for .loader").hide() + $("#trust_for button").attr("disabled",false) + } + $("#trust_for button").click(on_trust_for) } + $(document).ready( function() { - console.log("doc ready!") set_up_tx_sender() } ) diff --git a/tool/template-tx-sender.html b/tool/template-tx-sender.html index 15f7b199b0..9aba910686 100644 --- a/tool/template-tx-sender.html +++ b/tool/template-tx-sender.html @@ -29,28 +29,34 @@
- - - Send transactions to this XRP Test Net address + + + Send transactions to this XRP Test Net address

Send Transaction

-
+
- +
+ +
+
- - drops of XRP + + drops of XRP
Send a simple XRP-to-XRP payment. -
+

+
+ +
Delivers a small amount of XRP with a large Amount value, to test your handling of partial payments. @@ -58,17 +64,20 @@
-
+
- +
+ +
+
for - + seconds ( - - ) + + )
@@ -77,13 +86,16 @@
-
+
- +
+ +
+
funded with - - drops of XRP + + drops of XRP
Create a payment channel. @@ -91,25 +103,31 @@
-
+
- +
+ +
+
- - FOO + + FOO
- Your destination address should trust (sending address) for the currency in question. + Your destination address should trust (the test sender) for the currency in question.

-
+
- +
+ +
+
- - FOO + + FOO
The test sender creates a trust line to your account for the given currency. From 032e2d28b49bf57475f1f91be98e671bcdb53d89 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Tue, 23 Apr 2019 17:25:40 -0700 Subject: [PATCH 3/6] Tx sender: feature complete? - Send partial payments works - Shows progress bars for partial payment setup, escrow auto-release - Actually tracks sending address's XRP balance --- assets/js/tx-sender.js | 211 ++++++++++++++++++++++++++++++----- tool/template-tx-sender.html | 15 ++- 2 files changed, 198 insertions(+), 28 deletions(-) diff --git a/assets/js/tx-sender.js b/assets/js/tx-sender.js index cb4f48dbb2..a034945775 100644 --- a/assets/js/tx-sender.js +++ b/assets/js/tx-sender.js @@ -6,9 +6,11 @@ const set_up_tx_sender = async function() { FAUCET_URL = "https://faucet.altnet.rippletest.net/accounts" TESTNET_URL = "wss://s.altnet.rippletest.net:51233" - let sending_address = "" - let sending_secret = "" - let xrp_balance = 0 + let connection_ready = false + + let sending_address + let sending_secret + let xrp_balance console.debug("Getting a sending address from the faucet...") @@ -40,10 +42,12 @@ const set_up_tx_sender = async function() { api = new ripple.RippleAPI({server: TESTNET_URL}) api.on('connected', () => { + connection_ready = true $("#connection-status-item").text("Connected") $("#connection-status-item").removeClass("disabled").addClass("active") }) api.on('disconnected', (code) => { + connection_ready = false $("#connection-status-item").text("Not connected") $("#connection-status-item").removeClass("active").addClass("disabled") }) @@ -54,6 +58,11 @@ const set_up_tx_sender = async function() { // Generic Transaction Submission ////////////////////////////////////////////////////////////////////////////// + // Helper function for await-able timeouts + function timeout(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + const INTERVAL = 1000 // milliseconds to wait for new ledger versions async function verify_transaction(hash, options) { try { @@ -73,13 +82,22 @@ const set_up_tx_sender = async function() { } } - async function submit_and_verify(tx_object) { + async function update_xrp_balance() { + balances = await api.getBalances(sending_address, {currency: "XRP"}) + $("#balance-item").text(balances[0].value) + } + + async function submit_and_verify(tx_object, use_secret, silent) { + if (use_secret === undefined) { + use_secret = sending_secret + } try { // Auto-fill fields like Fee and Sequence prepared = await api.prepareTransaction(tx_object) + console.debug("Prepared:", prepared) } catch(error) { console.log(error) - alert("Error preparing tx: "+error) + if (!silent) { alert("Error preparing tx: "+error) } return } @@ -92,11 +110,11 @@ const set_up_tx_sender = async function() { try { // Sign, submit - sign_response = api.sign(prepared.txJSON, sending_secret) + sign_response = api.sign(prepared.txJSON, use_secret) await api.submit(sign_response.signedTransaction) } catch (error) { console.log(error) - alert("Error signing & submitting tx: "+error) + if (!silent) { alert("Error signing & submitting tx: "+error) } return } @@ -107,17 +125,121 @@ const set_up_tx_sender = async function() { // TODO: more "notification-like" system // TODO: output should link to a tx lookup/explainer if (final_result === "tesSUCCESS") { - alert("Tx succeeded (hash:"+sign_response.id+")") + if (!silent) { alert("Tx succeeded (hash:"+sign_response.id+")") } } else { - alert("Tx failed w/ code "+final_result+" (hash: "+sign_response.id+")") + if (!silent) { alert("Tx failed w/ code "+final_result+" (hash: "+sign_response.id+")") } } + update_xrp_balance() return data } catch(error) { - alert("Error submitting tx: "+error) + console.log(error) + if (!silent) { alert("Error submitting tx: "+error) } } } + ////////////////////////////////////////////////////////////////////////////// + // Issuer Setup for Partial Payments + // (Partial payments must involve at least one issued currency, so we set up + // an issuer for a fake currency to ripple through.) + ////////////////////////////////////////////////////////////////////////////// + + let pp_issuer_address + let pp_sending_currency = "BAR" + async function set_up_for_partial_payments() { + while (!connection_ready) { + console.debug("... waiting for connection before doing partial payment setup") + await timeout(200) + } + console.debug("Starting partial payment setup...") + $("#pp_progress .progress-bar").addClass("progress-bar-animated") + // 1. Get a funded address to use as issuer + let pp_issuer_secret + try { + const faucet_response = await ($.ajax({ + url: FAUCET_URL, + type: 'POST', + dataType: 'json' + })) + pp_issuer_address = faucet_response.account.address + pp_issuer_secret = faucet_response.account.secret + } catch(error) { + console.log("Error getting issuer address for partial payments:", error) + return + } + $("#pp_progress .progress-bar").width("20%") + + // 2. Set DefaultRipple on issuer + let resp = await submit_and_verify({ + TransactionType: "AccountSet", + Account: pp_issuer_address, + SetFlag: 8 + }, pp_issuer_secret, true) + if (resp === undefined) { + console.log("Couldn't set DefaultRipple for partial payment issuer") + return + } + $("#pp_progress .progress-bar").width("40%") + + // 3. Make a trust line from sending address to issuer + resp = await submit_and_verify({ + TransactionType: "TrustSet", + Account: sending_address, + LimitAmount: { + currency: pp_sending_currency, + value: "1000000000", // arbitrarily, 1 billion fake currency + issuer: pp_issuer_address + } + }, sending_secret, true) + if (resp === undefined) { + console.log("Error making trust line to partial payment issuer") + return + } + $("#pp_progress .progress-bar").width("60%") + + // 4. Issue fake currency to main sending address + resp = await submit_and_verify({ + TransactionType: "Payment", + Account: pp_issuer_address, + Destination: sending_address, + Amount: { + currency: pp_sending_currency, + value: "1000000000", + issuer: pp_issuer_address + } + }, pp_issuer_secret, true) + if (resp === undefined) { + console.log("Error sending fake currency from partial payment issuer") + return + } + $("#pp_progress .progress-bar").width("80%") + + // 5. Place offer to buy issued currency for XRP + // When sending the partial payment, the sender consumes their own offer (!) + // so they end up paying themselves issued currency then delivering XRP. + resp = await submit_and_verify({ + TransactionType: "OfferCreate", + Account: sending_address, + TakerGets: "1000000000000000", // 1 billion XRP + TakerPays: { + currency: pp_sending_currency, + value: "1000000000", + issuer: pp_issuer_address + } + }, sending_secret, true) + if (resp === undefined) { + console.log("Error placing order to enable partial payments") + return + } + $("#pp_progress .progress-bar").width("100%").removeClass("progress-bar-animated") + $("#pp_progress").hide() + + // Done. Enable "Send Partial Payment" button + console.log("Done getting ready to send partial payments.") + $("#send_partial_payment button").prop("disabled",false) + $("#send_partial_payment button").attr("title", "") + } + set_up_for_partial_payments() ////////////////////////////////////////////////////////////////////////////// // Button Handlers @@ -142,7 +264,39 @@ const set_up_tx_sender = async function() { $("#send_xrp_payment button").click(on_click_send_xrp_payment) // 2. Send Partial Payment Handler --------------------------------------- - // TODO + async function on_click_send_partial_payment(event) { + const destination_address = $("#destination_address").val() + $("#send_partial_payment .loader").show() + $("#send_partial_payment button").attr("disabled","disabled") + + // const path_find_result = await api.request("ripple_path_find", { + // source_account: sending_address, + // destination_account: destination_address, + // destination_amount: "-1", // as much XRP as possible + // source_currencies: [{currency: pp_sending_currency, issuer: pp_issuer_address}] + // }) + // console.log("Path find result:", path_find_result) + // use_path = path_find_result.alternatives[0].paths_computed + + await submit_and_verify({ + TransactionType: "Payment", + Account: sending_address, + Destination: destination_address, + Amount: "1000000000000000", // 1 billion XRP + SendMax: { + value: String(Math.random()*.01), // random very small amount + currency: pp_sending_currency, + issuer: pp_issuer_address + }, + Flags: 0x80020000 // tfPartialPayment | tfFullyCanonicalSig + /*, + Paths: use_path*/ + }) + $("#send_partial_payment .loader").hide() + $("#send_partial_payment button").attr("disabled",false) + } + $("#send_partial_payment button").click(on_click_send_partial_payment) + // 3. Create Escrow Handler ---------------------------------------------- async function on_click_create_escrow(event) { @@ -169,22 +323,29 @@ const set_up_tx_sender = async function() { if (release_auto) { // Wait until there's a ledger with a close time > FinishAfter - // Check at the FinishAfter time, then approx. every INTERVAL thereafter - function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); - } - let delay_ms = (finish_after - api.iso8601ToRippleTime(Date())) * 1000 - let latestCloseTimeRipple = null - while (delay_ms > 0) { - // console.log("Waiting another "+delay_ms+"ms for escrow to be ready") - await timeout(delay_ms) - latestCloseTimeRipple = api.iso8601ToRippleTime((await api.getLedger()).closeTime) - if (latestCloseTimeRipple > finish_after) { - delay_ms = 0 - } else { - delay_ms = 1000 + // to submit the EscrowFinish + $("#escrow_progress .progress-bar").width("0%").addClass("progress-bar-animated") + $("#escrow_progress").show() + let seconds_left + let pct_done + let latestCloseTimeRipple + while (true) { + seconds_left = (finish_after - api.iso8601ToRippleTime(Date())) + pct_done = Math.min(99, Math.max(0, (1-(seconds_left / duration_seconds)) * 100)) + $("#escrow_progress .progress-bar").width(pct_done+"%") + if (seconds_left <= 0) { + // System time has advanced past FinishAfter. But is there a new + // enough validated ledger? + latestCloseTimeRipple = api.iso8601ToRippleTime((await api.getLedger()).closeTime) + if (latestCloseTimeRipple > finish_after) { + $("#escrow_progress .progress-bar").width("100%").removeClass("progress-bar-animated") + break + } } + // Update the progress bar & check again in 1 second. + await timeout(1000) } + $("#escrow_progress").hide() // Now submit the EscrowFinish // Future feature: submit from a different sender, just to prove that diff --git a/tool/template-tx-sender.html b/tool/template-tx-sender.html index 9aba910686..8a082b12bf 100644 --- a/tool/template-tx-sender.html +++ b/tool/template-tx-sender.html @@ -46,18 +46,23 @@ drops of XRP
+
Send a simple XRP-to-XRP payment.

-
+
+
+
 
+ (Getting ready to send partial payments) +
- +
Delivers a small amount of XRP with a large Amount value, to test your handling of partial payments.
@@ -82,6 +87,10 @@
Create a time-based escrow of 1 XRP. +

@@ -114,7 +123,7 @@ FOO
- Your destination address should trust (the test sender) for the currency in question. + Your destination address needs a trust line to (the test sender) for the currency in question. Otherwise, you'll get tecPATH_DRY.

From 269d889fcf86beddf66cc626a70b9829a420876a Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Tue, 23 Apr 2019 18:50:19 -0700 Subject: [PATCH 4/6] Tx sender improvements - Stick to proper precision when forming partial payments - Use growl-like notifs to report tx success/failure --- assets/js/tx-sender.js | 77 ++++++++++++------ assets/vendor/bootstrap-growl.jquery.js | 101 ++++++++++++++++++++++++ tool/template-tx-sender.html | 3 +- 3 files changed, 155 insertions(+), 26 deletions(-) create mode 100644 assets/vendor/bootstrap-growl.jquery.js diff --git a/assets/js/tx-sender.js b/assets/js/tx-sender.js index a034945775..f3c418e532 100644 --- a/assets/js/tx-sender.js +++ b/assets/js/tx-sender.js @@ -1,10 +1,31 @@ const set_up_tx_sender = async function() { + ////////////////////////////////////////////////////////////////////////////// + // Notification helpers + ////////////////////////////////////////////////////////////////////////////// + + function successNotif(msg) { + $.bootstrapGrowl(msg, { + delay: 7000, + offset: {from: 'bottom', amount: 68}, + type: 'success', + width: 'auto' + }) + } + function errorNotif(msg) { + $.bootstrapGrowl(msg, { + delay: 7000, + offset: {from: 'bottom', amount: 68}, + type: 'danger', + width: 'auto' + }) + } + ////////////////////////////////////////////////////////////////////////////// // Connection / Setup ////////////////////////////////////////////////////////////////////////////// - FAUCET_URL = "https://faucet.altnet.rippletest.net/accounts" - TESTNET_URL = "wss://s.altnet.rippletest.net:51233" + const FAUCET_URL = "https://faucet.altnet.rippletest.net/accounts" + const TESTNET_URL = "wss://s.altnet.rippletest.net:51233" let connection_ready = false @@ -12,6 +33,7 @@ const set_up_tx_sender = async function() { let sending_secret let xrp_balance + console.debug("Getting a sending address from the faucet...") faucet_response = function(data) { @@ -25,20 +47,15 @@ const set_up_tx_sender = async function() { $(".sending-address-item").text(sending_address) } - // TEMP: reuse same address for testing - faucet_response({account:{address:"r6f2viHtMjNSfERbZmXXkJnMmkBAN6d9X", secret:"spyTc4y4GAQwBfQHCxiJ1Xd2mmnM2"},balance:10000}) - - // POST-TEMP: Version that actually uses the faucet on every run: - // $.ajax({ - // url: FAUCET_URL, - // type: 'POST', - // dataType: 'json', - // success: faucet_response, - // error: function() { - // alert("There was an error with the XRP Ledger Test Net Faucet. Reload this page to try again."); - // } - // }) - // + $.ajax({ + url: FAUCET_URL, + type: 'POST', + dataType: 'json', + success: faucet_response, + error: function() { + errorNotif("There was an error with the XRP Ledger Test Net Faucet. Reload this page to try again.") + } + }) api = new ripple.RippleAPI({server: TESTNET_URL}) api.on('connected', () => { @@ -97,7 +114,9 @@ const set_up_tx_sender = async function() { console.debug("Prepared:", prepared) } catch(error) { console.log(error) - if (!silent) { alert("Error preparing tx: "+error) } + if (!silent) { + errorNotif("Error preparing tx: "+error) + } return } @@ -114,7 +133,9 @@ const set_up_tx_sender = async function() { await api.submit(sign_response.signedTransaction) } catch (error) { console.log(error) - if (!silent) { alert("Error signing & submitting tx: "+error) } + if (!silent) { + errorNotif("Error signing & submitting "+tx_object.TransactionType+" tx: "+error) + } return } @@ -122,18 +143,24 @@ const set_up_tx_sender = async function() { try { const data = await verify_transaction(sign_response.id, options) const final_result = data.outcome.result - // TODO: more "notification-like" system - // TODO: output should link to a tx lookup/explainer + // Future feature: output should link to a TestNet tx lookup/explainer if (final_result === "tesSUCCESS") { - if (!silent) { alert("Tx succeeded (hash:"+sign_response.id+")") } + if (!silent) { + successNotif(tx_object.TransactionType+" tx succeeded (hash: "+sign_response.id+")") + } } else { - if (!silent) { alert("Tx failed w/ code "+final_result+" (hash: "+sign_response.id+")") } + if (!silent) { + errorNotif(tx_object.TransactionType+" tx failed w/ code "+final_result+ + " (hash: "+sign_response.id+")") + } } update_xrp_balance() return data } catch(error) { console.log(error) - if (!silent) { alert("Error submitting tx: "+error) } + if (!silent) { + errorNotif("Error submitting "+tx_object.TransactionType+" tx: "+error) + } } } @@ -284,7 +311,7 @@ const set_up_tx_sender = async function() { Destination: destination_address, Amount: "1000000000000000", // 1 billion XRP SendMax: { - value: String(Math.random()*.01), // random very small amount + value: (Math.random()*.01).toPrecision(15), // random very small amount currency: pp_sending_currency, issuer: pp_issuer_address }, @@ -306,7 +333,7 @@ const set_up_tx_sender = async function() { const duration_seconds = parseInt(duration_seconds_txt, 10) if (duration_seconds === NaN || duration_seconds < 1) { - alert("Error: Escrow duration must be a positive number of seconds") + errorNotif("Error: Escrow duration must be a positive number of seconds") return } const finish_after = api.iso8601ToRippleTime(Date()) + duration_seconds diff --git a/assets/vendor/bootstrap-growl.jquery.js b/assets/vendor/bootstrap-growl.jquery.js new file mode 100644 index 0000000000..9f4289f7f1 --- /dev/null +++ b/assets/vendor/bootstrap-growl.jquery.js @@ -0,0 +1,101 @@ +/* +The MIT License + +Copyright (c) Nick Larson, http://github.com/ifightcrime + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +(function() { + var $; + + $ = jQuery; + + $.bootstrapGrowl = function(message, options) { + var $alert, css, offsetAmount; + options = $.extend({}, $.bootstrapGrowl.default_options, options); + $alert = $("
"); + $alert.attr("class", "bootstrap-growl alert"); + if (options.type) { + $alert.addClass("alert-" + options.type); + } + if (options.allow_dismiss) { + $alert.addClass("alert-dismissible"); + $alert.append(""); + } + $alert.append(message); + if (options.top_offset) { + options.offset = { + from: "top", + amount: options.top_offset + }; + } + offsetAmount = options.offset.amount; + $(".bootstrap-growl").each(function() { + return offsetAmount = Math.max(offsetAmount, parseInt($(this).css(options.offset.from)) + $(this).outerHeight() + options.stackup_spacing); + }); + css = { + "position": (options.ele === "body" ? "fixed" : "absolute"), + "margin": 0, + "z-index": "9999", + "display": "none" + }; + css[options.offset.from] = offsetAmount + "px"; + $alert.css(css); + if (options.width !== "auto") { + $alert.css("width", options.width + "px"); + } + $(options.ele).append($alert); + switch (options.align) { + case "center": + $alert.css({ + "left": "50%", + "margin-left": "-" + ($alert.outerWidth() / 2) + "px" + }); + break; + case "left": + $alert.css("left", "20px"); + break; + default: + $alert.css("right", "20px"); + } + $alert.fadeIn(); + if (options.delay > 0) { + $alert.delay(options.delay).fadeOut(function() { + return $(this).alert("close"); + }); + } + return $alert; + }; + + $.bootstrapGrowl.default_options = { + ele: "body", + type: "info", + offset: { + from: "top", + amount: 20 + }, + align: "right", + width: 250, + delay: 4000, + allow_dismiss: true, + stackup_spacing: 10 + }; + +}).call(this); diff --git a/tool/template-tx-sender.html b/tool/template-tx-sender.html index 8a082b12bf..586d206ee5 100644 --- a/tool/template-tx-sender.html +++ b/tool/template-tx-sender.html @@ -150,5 +150,6 @@ {% block endbody %} - + + {% endblock %} From 71ee136cb04f943f4ff198bc731fc0bd98de58fe Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Wed, 24 Apr 2019 19:40:38 -0700 Subject: [PATCH 5/6] Tx sender: mobile improvements, history box --- assets/css/devportal.css | 25 +++++++++++++++++++++++++ assets/js/tx-sender.js | 21 +++++++++++++++++---- tool/template-tx-sender.html | 35 +++++++++++++++++++---------------- 3 files changed, 61 insertions(+), 20 deletions(-) diff --git a/assets/css/devportal.css b/assets/css/devportal.css index 489efdebb4..07e4ca8f79 100644 --- a/assets/css/devportal.css +++ b/assets/css/devportal.css @@ -1147,6 +1147,31 @@ a.current { width: 25px;height:25px; } +#connection-status-item.active { + background-color: #2BCB96; + border-color: #2BCB96; +} + +#tx-sender-history ul { + overflow: auto; + height: 220px; + border: 1px solid rgba(0, 0, 0, 0.125) +} + +#tx-sender-history .list-group-item { + font-size: small; + color: #6c757d; +} + +.page-tx-sender .input-group .form-control { + flex: 1 1 20%; +} + +.bootstrap-growl { + max-width: 90vw !important; + overflow: hidden; +} + /* Print styles ------------------------------------------------------------- */ @media print { diff --git a/assets/js/tx-sender.js b/assets/js/tx-sender.js index f3c418e532..f7305f5d52 100644 --- a/assets/js/tx-sender.js +++ b/assets/js/tx-sender.js @@ -20,6 +20,18 @@ const set_up_tx_sender = async function() { }) } + function logTx(txtype, hash, result) { + let li = "wtf" + // Future feature: link hash to a testnet txsplainer + if (result === "tesSUCCESS") { + li = '
  • '+txtype+": "+hash+'
  • ' + } else { + li = '
  • '+txtype+": "+hash+'
  • ' + } + + $("#tx-sender-history ul").prepend(li) + } + ////////////////////////////////////////////////////////////////////////////// // Connection / Setup ////////////////////////////////////////////////////////////////////////////// @@ -43,7 +55,7 @@ const set_up_tx_sender = async function() { // so this won't go over JavaScript's // 64-bit double precision - $("#balance-item").text(xrp_balance) + " drops" + $("#balance-item").text(xrp_balance) $(".sending-address-item").text(sending_address) } @@ -127,6 +139,7 @@ const set_up_tx_sender = async function() { maxLedgerVersion: prepared.instructions.maxLedgerVersion } + let sign_response try { // Sign, submit sign_response = api.sign(prepared.txJSON, use_secret) @@ -147,11 +160,13 @@ const set_up_tx_sender = async function() { if (final_result === "tesSUCCESS") { if (!silent) { successNotif(tx_object.TransactionType+" tx succeeded (hash: "+sign_response.id+")") + logTx(tx_object.TransactionType, sign_response.id, final_result) } } else { if (!silent) { errorNotif(tx_object.TransactionType+" tx failed w/ code "+final_result+ " (hash: "+sign_response.id+")") + logTx(tx_object.TransactionType, sign_response.id, final_result) } } update_xrp_balance() @@ -315,9 +330,7 @@ const set_up_tx_sender = async function() { currency: pp_sending_currency, issuer: pp_issuer_address }, - Flags: 0x80020000 // tfPartialPayment | tfFullyCanonicalSig - /*, - Paths: use_path*/ + Flags: api.txFlags.Payment.PartialPayment | api.txFlags.Universal.FullyCanonicalSig }) $("#send_partial_payment .loader").hide() $("#send_partial_payment button").attr("disabled",false) diff --git a/tool/template-tx-sender.html b/tool/template-tx-sender.html index 586d206ee5..65bfd2d8a0 100644 --- a/tool/template-tx-sender.html +++ b/tool/template-tx-sender.html @@ -16,6 +16,11 @@
  • Test XRP Available:
  • (None)
  • +
    +
    Transaction History
    +
      +
    +
    {% endblock %} @@ -42,8 +47,8 @@ +
    - drops of XRP
    @@ -75,18 +80,17 @@ +
    - for - seconds - - ( - - ) -
    + + ( + + ) + - Create a time-based escrow of 1 XRP. + Create a time-based escrow of 1 XRP for the specified number of seconds. +
    - funded with - drops of XRP
    - Create a payment channel. + Create a payment channel and fund it with the specified amount of XRP.
    @@ -118,12 +121,12 @@ +
    - FOO
    - Your destination address needs a trust line to (the test sender) for the currency in question. Otherwise, you'll get tecPATH_DRY. + Your destination address needs a trust line to (the test sender) for the currency in question. Otherwise, you'll get tecPATH_DRY.
    @@ -134,12 +137,12 @@ +
    - FOO
    - The test sender creates a trust line to your account for the given currency. + The test sender creates a trust line to your account for the given currency. From c5fbd4b52f0f3a5a7c75120d26f82503e9dd0be0 Mon Sep 17 00:00:00 2001 From: mDuo13 Date: Thu, 25 Apr 2019 13:01:32 -0700 Subject: [PATCH 6/6] Dev Tools: fix landing page URL, blurbs; add Tx sender --- content/dev-tools/dev-tools.md | 25 +++++--- dactyl-config.yml | 9 +++ tool/template-page-children.html | 1 + tool/template-redirect.html | 14 ++++ tool/template-sidebar_nav.html | 106 ++++++++++++++++--------------- 5 files changed, 94 insertions(+), 61 deletions(-) create mode 100644 tool/template-redirect.html diff --git a/content/dev-tools/dev-tools.md b/content/dev-tools/dev-tools.md index e7933edecd..bb1a78120d 100644 --- a/content/dev-tools/dev-tools.md +++ b/content/dev-tools/dev-tools.md @@ -5,24 +5,29 @@ Ripple provides a set of developer tools to help you test, explore, and validate * **[XRP Ledger Lookup Tool](xrp-ledger-rpc-tool.html)** - Use this JSON-RPC-based debugging tool to print raw information about a XRP Ledger account, transaction, or ledger. - -* **[XRP Ledger Test Net Faucet](xrp-test-net-faucet.html)** - - Use the WebSocket and JSON-RPC Test Net endpoints to test software built on the XRP Ledger without using real funds. Generate Test Net credentials and funds for testing purposes. Test net ledger and balances are reset on a regular basis. - + Use this JSON-RPC-based debugging tool to print raw information about a XRP Ledger account, transaction, or ledger. * **[rippled API WebSocket Tool](websocket-api-tool.html)** - Need to see the rippled API in action ASAP? Use this tool to send prepopulated sample requests and get responses. No setup required. - + Need to see the rippled API in action ASAP? Use this tool to send sample requests and get responses. No setup required. * **[Data API v2 Tool](data-api-v2-tool.html)** - Need to see the Data API v2 in action ASAP? Use this tool to send prepopulated sample requests and get responses. No setup required. + Need to see the Data API v2 in action ASAP? Use this tool to send prepopulated sample requests and get responses. No setup required. -* **[rippled.txt Validator](ripple-txt-validator.html)** +* **[XRP Ledger Test Net Faucet](xrp-test-net-faucet.html)** + + Use the WebSocket and JSON-RPC Test Net endpoints to test software built on the XRP Ledger without using real funds. Generate Test Net credentials and funds for testing purposes. Test Net ledger and balances are reset on a regular basis. + +* **[ripple.txt Validator](ripple-txt-validator.html)** Use this tool to verify that your `ripple.txt` is syntactically correct and deployed properly. + **Warning:** The `ripple.txt` file definition has been deprecated. Use an [xrp-ledger.toml file](xrp-ledger-toml.html) instead. + +* **[Transaction Sender](tx-sender.html)** + + Test how your code handles various XRP Ledger transactions by sending them over the Test Net to the address of your choice. + + Have an idea for a tool not provided here? [Contact us >](mailto:docs@ripple.com) diff --git a/dactyl-config.yml b/dactyl-config.yml index db9b98b630..c829b55716 100644 --- a/dactyl-config.yml +++ b/dactyl-config.yml @@ -2870,12 +2870,21 @@ pages: # Dev Tools -------------------------------------------------------------------- - md: dev-tools/dev-tools.md + html: dev-tools.html funnel: Dev Tools filters: - buttonize targets: - local + - name: Dev Tools # Redirect page for old broken URL + html: dev-tools-dev-tools.html + template: template-redirect.html + redirect_url: dev-tools.html + funnel: Dev Tools + targets: + - local + - name: RPC Tool funnel: Dev Tools html: xrp-ledger-rpc-tool.html diff --git a/tool/template-page-children.html b/tool/template-page-children.html index d9fbef90f7..ccabb35087 100644 --- a/tool/template-page-children.html +++ b/tool/template-page-children.html @@ -39,6 +39,7 @@ {% set printed_next_levels = [] %} {% for onepage in thosepages %} {% if onepage == parent %}{# pass #} + {% elif onepage.template == "template-redirect.html" %}{# don't list redirects #} {% elif next_level_field == None or (onepage[next_level_field] is undefined and next_level_field != "supercategory") %} {# direct child, print it! #}
  • {{onepage.name}}{% if show_blurbs and onepage.blurb is defined and indent_level == 1%}

    {{onepage.blurb}}

    {% endif %}
  • diff --git a/tool/template-redirect.html b/tool/template-redirect.html new file mode 100644 index 0000000000..6a00edcee4 --- /dev/null +++ b/tool/template-redirect.html @@ -0,0 +1,14 @@ +{% extends "template-base.html" %} +{% block head %} + + +{% endblock %} + + +{% block main %} + +{% endblock %} diff --git a/tool/template-sidebar_nav.html b/tool/template-sidebar_nav.html index 7708cfd23c..2d51d3662c 100644 --- a/tool/template-sidebar_nav.html +++ b/tool/template-sidebar_nav.html @@ -38,6 +38,7 @@