mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-21 12:15:50 +00:00
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
This commit is contained in:
@@ -6,9 +6,11 @@ const set_up_tx_sender = async function() {
|
|||||||
FAUCET_URL = "https://faucet.altnet.rippletest.net/accounts"
|
FAUCET_URL = "https://faucet.altnet.rippletest.net/accounts"
|
||||||
TESTNET_URL = "wss://s.altnet.rippletest.net:51233"
|
TESTNET_URL = "wss://s.altnet.rippletest.net:51233"
|
||||||
|
|
||||||
let sending_address = ""
|
let connection_ready = false
|
||||||
let sending_secret = ""
|
|
||||||
let xrp_balance = 0
|
let sending_address
|
||||||
|
let sending_secret
|
||||||
|
let xrp_balance
|
||||||
|
|
||||||
console.debug("Getting a sending address from the faucet...")
|
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 = new ripple.RippleAPI({server: TESTNET_URL})
|
||||||
api.on('connected', () => {
|
api.on('connected', () => {
|
||||||
|
connection_ready = true
|
||||||
$("#connection-status-item").text("Connected")
|
$("#connection-status-item").text("Connected")
|
||||||
$("#connection-status-item").removeClass("disabled").addClass("active")
|
$("#connection-status-item").removeClass("disabled").addClass("active")
|
||||||
})
|
})
|
||||||
api.on('disconnected', (code) => {
|
api.on('disconnected', (code) => {
|
||||||
|
connection_ready = false
|
||||||
$("#connection-status-item").text("Not connected")
|
$("#connection-status-item").text("Not connected")
|
||||||
$("#connection-status-item").removeClass("active").addClass("disabled")
|
$("#connection-status-item").removeClass("active").addClass("disabled")
|
||||||
})
|
})
|
||||||
@@ -54,6 +58,11 @@ const set_up_tx_sender = async function() {
|
|||||||
// Generic Transaction Submission
|
// 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
|
const INTERVAL = 1000 // milliseconds to wait for new ledger versions
|
||||||
async function verify_transaction(hash, options) {
|
async function verify_transaction(hash, options) {
|
||||||
try {
|
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 {
|
try {
|
||||||
// Auto-fill fields like Fee and Sequence
|
// Auto-fill fields like Fee and Sequence
|
||||||
prepared = await api.prepareTransaction(tx_object)
|
prepared = await api.prepareTransaction(tx_object)
|
||||||
|
console.debug("Prepared:", prepared)
|
||||||
} catch(error) {
|
} catch(error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
alert("Error preparing tx: "+error)
|
if (!silent) { alert("Error preparing tx: "+error) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,11 +110,11 @@ const set_up_tx_sender = async function() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Sign, submit
|
// Sign, submit
|
||||||
sign_response = api.sign(prepared.txJSON, sending_secret)
|
sign_response = api.sign(prepared.txJSON, use_secret)
|
||||||
await api.submit(sign_response.signedTransaction)
|
await api.submit(sign_response.signedTransaction)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
alert("Error signing & submitting tx: "+error)
|
if (!silent) { alert("Error signing & submitting tx: "+error) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,17 +125,121 @@ const set_up_tx_sender = async function() {
|
|||||||
// TODO: more "notification-like" system
|
// TODO: more "notification-like" system
|
||||||
// TODO: output should link to a tx lookup/explainer
|
// TODO: output should link to a tx lookup/explainer
|
||||||
if (final_result === "tesSUCCESS") {
|
if (final_result === "tesSUCCESS") {
|
||||||
alert("Tx succeeded (hash:"+sign_response.id+")")
|
if (!silent) { alert("Tx succeeded (hash:"+sign_response.id+")") }
|
||||||
} else {
|
} 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
|
return data
|
||||||
} catch(error) {
|
} 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
|
// Button Handlers
|
||||||
@@ -142,7 +264,39 @@ const set_up_tx_sender = async function() {
|
|||||||
$("#send_xrp_payment button").click(on_click_send_xrp_payment)
|
$("#send_xrp_payment button").click(on_click_send_xrp_payment)
|
||||||
|
|
||||||
// 2. Send Partial Payment Handler ---------------------------------------
|
// 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 ----------------------------------------------
|
// 3. Create Escrow Handler ----------------------------------------------
|
||||||
async function on_click_create_escrow(event) {
|
async function on_click_create_escrow(event) {
|
||||||
@@ -169,22 +323,29 @@ const set_up_tx_sender = async function() {
|
|||||||
|
|
||||||
if (release_auto) {
|
if (release_auto) {
|
||||||
// Wait until there's a ledger with a close time > FinishAfter
|
// Wait until there's a ledger with a close time > FinishAfter
|
||||||
// Check at the FinishAfter time, then approx. every INTERVAL thereafter
|
// to submit the EscrowFinish
|
||||||
function timeout(ms) {
|
$("#escrow_progress .progress-bar").width("0%").addClass("progress-bar-animated")
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
$("#escrow_progress").show()
|
||||||
}
|
let seconds_left
|
||||||
let delay_ms = (finish_after - api.iso8601ToRippleTime(Date())) * 1000
|
let pct_done
|
||||||
let latestCloseTimeRipple = null
|
let latestCloseTimeRipple
|
||||||
while (delay_ms > 0) {
|
while (true) {
|
||||||
// console.log("Waiting another "+delay_ms+"ms for escrow to be ready")
|
seconds_left = (finish_after - api.iso8601ToRippleTime(Date()))
|
||||||
await timeout(delay_ms)
|
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)
|
latestCloseTimeRipple = api.iso8601ToRippleTime((await api.getLedger()).closeTime)
|
||||||
if (latestCloseTimeRipple > finish_after) {
|
if (latestCloseTimeRipple > finish_after) {
|
||||||
delay_ms = 0
|
$("#escrow_progress .progress-bar").width("100%").removeClass("progress-bar-animated")
|
||||||
} else {
|
break
|
||||||
delay_ms = 1000
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Update the progress bar & check again in 1 second.
|
||||||
|
await timeout(1000)
|
||||||
|
}
|
||||||
|
$("#escrow_progress").hide()
|
||||||
|
|
||||||
// Now submit the EscrowFinish
|
// Now submit the EscrowFinish
|
||||||
// Future feature: submit from a different sender, just to prove that
|
// Future feature: submit from a different sender, just to prove that
|
||||||
|
|||||||
@@ -46,18 +46,23 @@
|
|||||||
<input id="send_xrp_payment_amount" class="form-control" type="number" aria-describedby="send_xrp_payment_amount_help" value="100000" min="1" max="10000000000" />
|
<input id="send_xrp_payment_amount" class="form-control" type="number" aria-describedby="send_xrp_payment_amount_help" value="100000" min="1" max="10000000000" />
|
||||||
<span class="input-group-text" id="send_xrp_payment_amount_help">drops of XRP</span>
|
<span class="input-group-text" id="send_xrp_payment_amount_help">drops of XRP</span>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Future feature: Optional custom destination tag -->
|
||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted">Send a <a href="send-xrp.html">simple XRP-to-XRP payment</a>.</small>
|
<small class="form-text text-muted">Send a <a href="send-xrp.html">simple XRP-to-XRP payment</a>.</small>
|
||||||
</div><!-- /#send_xrp_payment -->
|
</div><!-- /#send_xrp_payment -->
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group" id="send_partial_payment">
|
||||||
|
<div class="progress mb-1" id="pp_progress">
|
||||||
|
<div class="progress-bar progress-bar-striped w-0"> </div>
|
||||||
|
<small class="justify-content-center d-flex position-absolute w-100">(Getting ready to send partial payments)</small>
|
||||||
|
</div>
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text loader" style="display: none"><img class="throbber" src="assets/img/rippleThrobber.png" /></span>
|
<span class="input-group-text loader" style="display: none"><img class="throbber" src="assets/img/rippleThrobber.png" /></span>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary form-control" type="button" id="sendPartialPaymentButton">Send Partial Payment</button>
|
<button class="btn btn-primary form-control" type="button" id="send_partial_payment_btn" disabled="disabled" autocomplete="off" title="(Please wait for partial payments setup to finish)">Send Partial Payment</button>
|
||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted">Delivers a small amount of XRP with a large <code>Amount</code> value, to test your handling of <a href="partial-payments.html">partial payments</a>.</small>
|
<small class="form-text text-muted">Delivers a small amount of XRP with a large <code>Amount</code> value, to test your handling of <a href="partial-payments.html">partial payments</a>.</small>
|
||||||
</div><!-- /.form group for partial payment -->
|
</div><!-- /.form group for partial payment -->
|
||||||
@@ -82,6 +87,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted">Create a time-based escrow of 1 XRP.</small>
|
<small class="form-text text-muted">Create a time-based escrow of 1 XRP.</small>
|
||||||
|
<div class="progress mb-1" style="display:none" id="escrow_progress">
|
||||||
|
<div class="progress-bar progress-bar-striped w-0"> </div>
|
||||||
|
<small class="justify-content-center d-flex position-absolute w-100">(Waiting to release Escrow when it's ready)</small>
|
||||||
|
</div>
|
||||||
</div><!-- /.form group for create escrow -->
|
</div><!-- /.form group for create escrow -->
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
@@ -114,7 +123,7 @@
|
|||||||
<span class="input-group-text" id="send_issued_currency_code">FOO</span><!-- TODO: custom currency codes -->
|
<span class="input-group-text" id="send_issued_currency_code">FOO</span><!-- TODO: custom currency codes -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small class="form-text text-muted">Your destination address should trust <span class="sending-address-item">(the test sender)</span> for the currency in question.</small>
|
<small class="form-text text-muted">Your destination address needs a trust line to <span class="sending-address-item">(the test sender)</span> for the currency in question. Otherwise, you'll get tecPATH_DRY.</small>
|
||||||
</div><!-- /.form group for issued currency payment -->
|
</div><!-- /.form group for issued currency payment -->
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
Reference in New Issue
Block a user