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.