Merge pull request #561 from mDuo13/tx_sender_tool

Transaction sender tool
This commit is contained in:
Rome Reginelli
2019-05-01 18:24:58 -07:00
committed by GitHub
9 changed files with 864 additions and 61 deletions

View File

@@ -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;
@@ -1146,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 {

478
assets/js/tx-sender.js Normal file
View File

@@ -0,0 +1,478 @@
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'
})
}
function logTx(txtype, hash, result) {
let li = "wtf"
// Future feature: link hash to a testnet txsplainer
if (result === "tesSUCCESS") {
li = '<li class="list-group-item fade-in p-1 text-muted"><i class="fa fa-check-circle"></i> '+txtype+": "+hash+'</li>'
} else {
li = '<li class="list-group-item fade-in p-1 list-group-item-danger"><i class="fa fa-times-circle"></i> '+txtype+": "+hash+'</li>'
}
$("#tx-sender-history ul").prepend(li)
}
//////////////////////////////////////////////////////////////////////////////
// Connection / Setup
//////////////////////////////////////////////////////////////////////////////
const FAUCET_URL = "https://faucet.altnet.rippletest.net/accounts"
const TESTNET_URL = "wss://s.altnet.rippletest.net:51233"
let connection_ready = false
let sending_address
let sending_secret
let xrp_balance
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)
$(".sending-address-item").text(sending_address)
}
$.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', () => {
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")
})
console.log("Connecting to Test Net WebSocket...")
api.connect()
//////////////////////////////////////////////////////////////////////////////
// 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 {
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 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)
if (!silent) {
errorNotif("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
}
let sign_response
try {
// Sign, submit
sign_response = api.sign(prepared.txJSON, use_secret)
await api.submit(sign_response.signedTransaction)
} catch (error) {
console.log(error)
if (!silent) {
errorNotif("Error signing & submitting "+tx_object.TransactionType+" 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
// Future feature: output should link to a TestNet tx lookup/explainer
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()
return data
} catch(error) {
console.log(error)
if (!silent) {
errorNotif("Error submitting "+tx_object.TransactionType+" 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
//////////////////////////////////////////////////////////////////////////////
// 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 ---------------------------------------
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: (Math.random()*.01).toPrecision(15), // random very small amount
currency: pp_sending_currency,
issuer: pp_issuer_address
},
Flags: api.txFlags.Payment.PartialPayment | api.txFlags.Universal.FullyCanonicalSig
})
$("#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) {
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) {
errorNotif("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
// 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
// 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() {
set_up_tx_sender()
} )

101
assets/vendor/bootstrap-growl.jquery.js vendored Normal file
View File

@@ -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 = $("<div>");
$alert.attr("class", "bootstrap-growl alert");
if (options.type) {
$alert.addClass("alert-" + options.type);
}
if (options.allow_dismiss) {
$alert.addClass("alert-dismissible");
$alert.append("<button class=\"close\" data-dismiss=\"alert\" type=\"button\"><span aria-hidden=\"true\">&#215;</span><span class=\"sr-only\">Close</span></button>");
}
$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);

View File

@@ -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.
<!--{# TODO: For information about how to connect your `rippled` test server to the Test Net, see [XXXXX](x). #}-->
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.
<!--{# TODO: which methods are surfaced here -- is this all of them? #}-->
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)

View File

@@ -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
@@ -2915,6 +2924,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

View File

@@ -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! #}
<li class="level-{{indent_level}}"><a href="{{onepage.html}}">{{onepage.name}}</a>{% if show_blurbs and onepage.blurb is defined and indent_level == 1%}<p class="blurb child-blurb">{{onepage.blurb}}</p>{% endif %}</li>

View File

@@ -0,0 +1,14 @@
{% extends "template-base.html" %}
{% block head %}
<meta http-equiv="refresh" content="0;url={{currentpage.redirect_url}}" />
{% endblock %}
{% block main %}
<article class="p-3">
<div class="content">
<p>This page has been moved! You should be redirected automatically. If not, <a href="{{currentpage.redirect_url}}">click here to go to the new {{currentpage.name}} page</a>.
</div>
</article>
{% endblock %}

View File

@@ -38,6 +38,7 @@
<ul class="sidebar_pagelist">
{% for page in funnelpages %}
{% if loop.index == 1 %}{# Skip the first element since it's linked by the funnel header #}
{% elif page.template == "template-redirect.html" %}{# skip redirects #}
{% elif page == currentpage %}
<li><a class="active nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
@@ -82,33 +83,34 @@
{% set printed_subcategories = [] %}
{% for page in catpages %}
{% if loop.index != 1 %}{# Skip the first element since it's linked by the category header #}
{% if page.subcategory is undefined %}
{% if page == currentpage %}
<li><a class="active nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% elif page.subcategory not in printed_subcategories %}
{% if page == currentpage %}
<li><a class="subcat-title active" href="#main_content_body">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% elif currentpage.subcategory is defined and page.subcategory == currentpage.subcategory %}
<li><a class="subcat-title active-parent" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="subcat-title" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% for subpage in catpages|selectattr('subcategory', 'defined_and_equalto', page.subcategory) %}
{% if subpage != page %}
{% if subpage == currentpage %}
<li><a class="active subpage" href="#main_content_body">{{ subpage.name }}{% if subpage.status is defined and subpage.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="subpage" href="{{ subpage.html }}">{{ subpage.name }}{% if subpage.status is defined and subpage.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% if page.template == "template-redirect.html" %}{# skip redirects #}
{% elif page.subcategory is undefined %}
{% if page == currentpage %}
<li><a class="active nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% elif page.subcategory not in printed_subcategories %}
{% if page == currentpage %}
<li><a class="subcat-title active" href="#main_content_body">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% elif currentpage.subcategory is defined and page.subcategory == currentpage.subcategory %}
<li><a class="subcat-title active-parent" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="subcat-title" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% endfor %}
{% set _ = printed_subcategories.append(page.subcategory) %}
{% endif %}
{% for subpage in catpages|selectattr('subcategory', 'defined_and_equalto', page.subcategory) %}
{% if subpage != page %}
{% if subpage == currentpage %}
<li><a class="active subpage" href="#main_content_body">{{ subpage.name }}{% if subpage.status is defined and subpage.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="subpage" href="{{ subpage.html }}">{{ subpage.name }}{% if subpage.status is defined and subpage.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% set _ = printed_subcategories.append(page.subcategory) %}
{% endif %}
{% endif %}
{% endfor %}
@@ -154,7 +156,8 @@
{% set printed_categories = [] %}
{% for subpage in supercatpages %}
{% if loop.index != 1 %}{# Skip the first element since it's linked by the supercategory header #}
{% if subpage.category not in printed_categories %}
{% if subpage.template == "template-redirect.html" %}{# skip redirects #}
{% elif subpage.category not in printed_categories %}
<li><a class="subcat-title" href="{{ subpage.html }}">{{ subpage.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% set category_members = supercatpages|selectattr('category', 'defined_and_equalto', subpage.category)|list %}
@@ -204,33 +207,34 @@
{% set printed_subcategories = [] %}
{% for page in catpages %}
{% if loop.index != 1 %}{# Skip the first element since it's linked by the category header #}
{% if page.subcategory is undefined %}
{% if page == currentpage %}
<li><a class="active nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% elif page.subcategory not in printed_subcategories %}
{% if page == currentpage %}
<li><a class="subcat-title active" href="#main_content_body">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% elif page.subcategory is defined and currentpage.subcategory is defined and page.subcategory == currentpage.subcategory %}
<li><a class="subcat-title active-parent" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="subcat-title" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% for subpage in catpages|selectattr('subcategory', 'defined_and_equalto', page.subcategory) %}
{% if subpage != page %}
{% if subpage == currentpage %}
<li><a class="active subpage" href="#main_content_body">{{ subpage.name }}{% if subpage.status is defined and subpage.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="subpage" href="{{ subpage.html }}">{{ subpage.name }}{% if subpage.status is defined and subpage.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% if page.template == "template-redirect.html" %}{# skip redirects #}
{% elif page.subcategory is undefined %}
{% if page == currentpage %}
<li><a class="active nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="nosubcat-page" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% elif page.subcategory not in printed_subcategories %}
{% if page == currentpage %}
<li><a class="subcat-title active" href="#main_content_body">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% elif page.subcategory is defined and currentpage.subcategory is defined and page.subcategory == currentpage.subcategory %}
<li><a class="subcat-title active-parent" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="subcat-title" href="{{ page.html }}">{{ page.name }}{% if page.status is defined and page.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% endfor %}
{% set _ = printed_subcategories.append(page.subcategory) %}
{% endif %}
{% for subpage in catpages|selectattr('subcategory', 'defined_and_equalto', page.subcategory) %}
{% if subpage != page %}
{% if subpage == currentpage %}
<li><a class="active subpage" href="#main_content_body">{{ subpage.name }}{% if subpage.status is defined and subpage.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% else %}
<li><a class="subpage" href="{{ subpage.html }}">{{ subpage.name }}{% if subpage.status is defined and subpage.status == "not_enabled" %} {% include 'template-status_not_enabled.html' %}{% endif %}</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% set _ = printed_subcategories.append(page.subcategory) %}
{% endif %}
{% endif %}
{% endfor %}

View File

@@ -0,0 +1,158 @@
{% extends "template-base.html" %}
{% block bodyclasses %}page-tx-sender{% endblock %}
{% block right_sidebar %}
<div id="connection-status" class="card">
<div class="card-header bg-dark">
<h3 class="card-title">Status</h3>
</div><!--/.card-header-->
<div class="card-body">
<ul class="list-group list-group-flush">
<li class="list-group-item" id="connection-status-label">XRP Test Net:</li>
<li class="list-group-item disabled" id="connection-status-item">Not Connected</li>
<li class="list-group-item" id="sending-address-label">Sending Address:</li>
<li class="list-group-item disabled sending-address-item">(None)</li>
<li class="list-group-item" id="balance-label">Test XRP Available:</li>
<li class="list-group-item disabled" id="balance-item">(None)</li>
</ul>
<div id="tx-sender-history">
<h5 class="m-3">Transaction History</h5>
<ul class="list-group list-group-flush">
</ul>
</div>
</div>
</div>
{% endblock %}
{% block main %}
<section class="container-fluid p-3">
<h1>Transaction Sender</h1>
<div class="content">
<p>This tool sends transactions to the <a href="xrp-test-net-faucet.html">XRP Test Net</a> address of your choice so you can test how you monitor and respond to incoming transactions.</p>
<form>
<div class="form-group">
<label for="destination_address">Destination Address</label>
<input type="text" class="form-control" id="destination_address" aria-describedby="destination_address_help" value="rUCzEr6jrEyMpjhs4wSdQdz4g8Y382NxfM" />
<small id="destination_address_help" class="form-text text-muted">Send transactions to this XRP Test Net address</small>
</div>
<h3>Send Transaction</h3>
<div class="form-group" id="send_xrp_payment">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text loader" style="display: none"><img class="throbber" src="assets/img/rippleThrobber.png" /></span>
</div>
<button class="btn btn-primary form-control" type="button" id="send_xrp_payment_btn">Send XRP Payment</button>
<input id="send_xrp_payment_amount" class="form-control" type="number" aria-describedby="send_xrp_payment_amount_help" value="100000" min="1" max="10000000000" />
<div class="input-group-append">
<span class="input-group-text" id="send_xrp_payment_amount_help">drops of XRP</span>
</div>
<!-- Future feature: Optional custom destination tag -->
</div>
<small class="form-text text-muted">Send a <a href="send-xrp.html">simple XRP-to-XRP payment</a>.</small>
</div><!-- /#send_xrp_payment -->
<hr />
<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">&nbsp;</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-prepend">
<span class="input-group-text loader" style="display: none"><img class="throbber" src="assets/img/rippleThrobber.png" /></span>
</div>
<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>
<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 -->
<hr />
<div class="form-group" id="create_escrow">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text loader" style="display: none"><img class="throbber" src="assets/img/rippleThrobber.png" /></span>
</div>
<button class="btn btn-primary form-control" type="button" id="create_escrow_btn">Create Escrow</button>
<input class="form-control" type="number" value="60" min="5" max="10000" id="create_escrow_duration_seconds" />
<div class="input-group-append">
<span class="input-group-text">seconds</span>
</div>
<span class="input-group-text">
(
<input type="checkbox" id="create_escrow_release_automatically" value="1" />
<label class="form-check-label" for="create_escrow_release_automatically">Finish automatically</label>)
</span>
</div>
<small class="form-text text-muted">Create a <a href="escrow.html">time-based escrow</a> of 1 XRP for the specified number of seconds.</small>
<div class="progress mb-1" style="display:none" id="escrow_progress">
<div class="progress-bar progress-bar-striped w-0">&nbsp;</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 -->
<hr />
<div class="form-group" id="create_payment_channel">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text loader" style="display: none"><img class="throbber" src="assets/img/rippleThrobber.png" /></span>
</div>
<button class="btn btn-primary form-control" type="button" id="create_payment_channel_btn">Create Payment Channel</button>
<input id="create_payment_channel_amount" class="form-control" type="number" aria-describedby="create_payment_channel_amount_help" value="100000" min="1" max="10000000000" />
<div class="input-group-append">
<span class="input-group-text" id="create_payment_channel_amount_help">drops of XRP</span>
</div>
</div>
<small class="form-text text-muted">Create a <a href="payment-channels.html">payment channel</a> and fund it with the specified amount of XRP.</small>
</div><!-- /.form group for create paychan -->
<hr />
<div class="form-group" id="send_issued_currency">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text loader" style="display: none"><img class="throbber" src="assets/img/rippleThrobber.png" /></span>
</div>
<button class="btn btn-primary form-control" type="button" id="send_issued_currency_btn">Send Issued Currency</button>
<input id="send_issued_currency_amount" class="form-control" type="text" value="100" /><!-- Note: HTML limits "number" inputs to IEEE 764 double precision, which isn't enough for the full range of issued currency amounts -->
<div class="input-group-append">
<span class="input-group-text" id="send_issued_currency_code">FOO</span><!-- TODO: custom currency codes -->
</div>
</div>
<small class="form-text text-muted">Your destination address needs a <a href="trust-lines-and-issuing.html">trust line</a> 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 -->
<hr />
<div class="form-group" id="trust_for">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text loader" style="display: none"><img class="throbber" src="assets/img/rippleThrobber.png" /></span>
</div>
<button class="btn btn-primary form-control" type="button" id="trust_for_btn">Trust for</button>
<input id="trust_for_amount" class="form-control disabled" type="number" value="100000" />
<div class="input-group-append">
<span class="input-group-text" id="trust_for_currency_code">FOO</span>
</div>
</div>
<small class="form-text text-muted">The test sender creates a <a href="trust-lines-and-issuing.html">trust line</a> to your account for the given currency.</small>
</div><!-- /.form group for create trust line -->
</form>
</div>
</section>
{% endblock %}
{% block endbody %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
<script type="application/javascript" src="assets/js/ripple-lib-1.1.2-min.js"></script>
<script type="application/javascript" src="assets/vendor/bootstrap-growl.jquery.js"></script>
<script type='application/javascript' src='assets/js/tx-sender.js'></script>
{% endblock %}