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.