mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-29 08:05:49 +00:00
Merge pull request #1143 from XRPLF/issue_token_tutorial
"Issue a token" tutorial
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -114,6 +114,7 @@ function complete_step_by_id(step_id) {
|
||||
".interactive-block").eq(0).find(".previous-steps-required")
|
||||
next_ui.prop("title", "")
|
||||
next_ui.prop("disabled", false)
|
||||
next_ui.removeClass("disabled")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,9 +148,9 @@ function pretty_print(j) {
|
||||
*/
|
||||
function disable_followup_steps() {
|
||||
$(".previous-steps-required").prop("title", tl("Complete all previous steps first"))
|
||||
$(".previous-steps-required").prop("disabled", true)
|
||||
$(".previous-steps-required").prop("disabled", true).addClass("disabled")
|
||||
$(".connection-required").prop("title", tl("Conection to the XRP Ledger required"))
|
||||
$(".connection-required").prop("disabled", true)
|
||||
$(".connection-required").prop("disabled", true).addClass("disabled")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,7 +361,6 @@ function setup_wait_steps() {
|
||||
min_ledger,
|
||||
max_ledger
|
||||
})
|
||||
console.log(tx_result)
|
||||
|
||||
if (tx_result.validated) {
|
||||
status_box.html(
|
||||
@@ -425,6 +425,54 @@ async function activate_wait_step(step_name, prelim_result) {
|
||||
status_box.data("status_pending", true)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the hexadecimal ASCII representation of a string (must contain only
|
||||
* 7-bit ASCII characters).
|
||||
* @param {String} s The string to encode.
|
||||
* @return {String} The uppercase hexadecimal representation of the string.
|
||||
*/
|
||||
function text_to_hex(s) {
|
||||
result = ""
|
||||
for (let i=0; i<s.length; i++) {
|
||||
result += s.charCodeAt(i).toString(16)
|
||||
}
|
||||
return result.toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a memo to transaction instructions (before signing) to indicate that this
|
||||
* transaction was generated by an interactive tutorial. This allows anyone to
|
||||
* observe transactions on Testnet/Devnet to see which ones originate from which
|
||||
* interactive tutorials. For privacy reasons, the memo does not and MUST NOT
|
||||
* include personally identifying information about the user or their browser.
|
||||
* @param {Object} event The click event that caused this transaction to be sent
|
||||
* @param {Object} tx_json The JSON transaction instructions to have the memo
|
||||
* added to them (in-place).
|
||||
*/
|
||||
function add_memo(event, tx_json) {
|
||||
const tutorial_info = {
|
||||
"path": window.location.pathname,
|
||||
"button": event.target.id
|
||||
}
|
||||
|
||||
const memo = {
|
||||
"Memo": {
|
||||
"MemoData": text_to_hex(JSON.stringify(tutorial_info, null, 0)),
|
||||
"MemoFormat": "6170706C69636174696F6E2F6A736F6E", // application/json
|
||||
// The MemoType decodes to a URL that explains the format of this memo type:
|
||||
// https://github.com/XRPLF/xrpl-dev-portal/blob/master/tool/INTERACTIVE_TUTORIALS_README.md
|
||||
"MemoType": "68747470733A2F2F6769746875622E636F6D2F5852504C462F7872706C2D6465762D706F7274616C2F626C6F622F6D61737465722F746F6F6C2F494E5445524143544956455F5455544F5249414C535F524541444D452E6D64"
|
||||
}
|
||||
}
|
||||
|
||||
if (tx_json.Memos === undefined) {
|
||||
tx_json["Memos"] = [memo]
|
||||
} else {
|
||||
tx_json["Memos"].push(memo)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for "Send Transaction" buttons to handle the full process of
|
||||
* Prepare → Sign → Submit in one step with appropriate outputs. Assumes you are
|
||||
@@ -441,15 +489,23 @@ async function activate_wait_step(step_name, prelim_result) {
|
||||
* @param {Event} event The (click) event that this is helping to handle.
|
||||
* @param {Object} tx_json JSON object of transaction instructions to finish
|
||||
* preparing and send.
|
||||
* @param {String} secret (Optional) The base58 seed to use to sign the
|
||||
* transaction. If omitted, look up the #use-secret field
|
||||
* which was probably added by a "Get Credentials" step.
|
||||
*/
|
||||
async function generic_full_send(event, tx_json) {
|
||||
async function generic_full_send(event, tx_json, secret) {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
const blob_selector = $(event.target).data("txBlobFrom")
|
||||
const wait_step_name = $(event.target).data("waitStepName")
|
||||
block.find(".output-area").html("")
|
||||
const secret = get_secret(event)
|
||||
|
||||
if (secret === undefined) {
|
||||
secret = get_secret(event)
|
||||
}
|
||||
if (!secret) {return}
|
||||
|
||||
add_memo(event, tx_json)
|
||||
|
||||
block.find(".loader").show()
|
||||
const prepared = await api.prepareTransaction(tx_json, {
|
||||
maxLedgerVersionOffset: 20
|
||||
@@ -511,6 +567,7 @@ async function do_submit(block, submit_opts, wait_step_name) {
|
||||
if (wait_step_name){
|
||||
activate_wait_step(wait_step_name, prelim_result)
|
||||
}
|
||||
return prelim_result
|
||||
} catch(error) {
|
||||
block.find(".loader").hide()
|
||||
show_error(block, error)
|
||||
|
||||
354
assets/js/tutorials/issue-a-token.js
Normal file
354
assets/js/tutorials/issue-a-token.js
Normal file
@@ -0,0 +1,354 @@
|
||||
// Variant setup for generate creds button from interactive-tutorial.js.
|
||||
// This version generates two sets of creds, one for the issuer and one for
|
||||
// the hot wallet / receiver
|
||||
|
||||
const EXAMPLE_COLD_ADDR = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"
|
||||
const EXAMPLE_COLD_SECRET = "sIss█████████████████████████"
|
||||
function setup_2x_generate_step() {
|
||||
|
||||
$("#generate-2x-creds-button").click( async (event) => {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
block.find(".output-area").html("")
|
||||
block.find(".loader").show()
|
||||
// Get faucet URL (Testnet/Devnet/etc.)
|
||||
const faucet_url = $("#generate-2x-creds-button").data("fauceturl")
|
||||
|
||||
try {
|
||||
const data = await call_faucet(faucet_url)
|
||||
const data2 = await call_faucet(faucet_url)
|
||||
|
||||
block.find(".loader").hide()
|
||||
block.find(".output-area").html(`<div class="row">
|
||||
<div class="col-xl-6 p-3">
|
||||
<div><strong>${tl("Cold Address:")}</strong>
|
||||
<span id="cold-use-address">${data.account.address}</span></div>
|
||||
<div><strong>${tl("Cold Secret:")}</strong>
|
||||
<span id="cold-use-secret">${data.account.secret}</span></div>
|
||||
<strong>${tl("XRP Balance:")}</strong>
|
||||
${Number(data.balance).toLocaleString(current_locale)} XRP
|
||||
</div>
|
||||
<div class="col-xl-6 p-3">
|
||||
<div><strong>${tl("Hot Address:")}</strong>
|
||||
<span id="hot-use-address">${data2.account.address}</span></div>
|
||||
<div><strong>${tl("Hot Secret:")}</strong>
|
||||
<span id="hot-use-secret">${data2.account.secret}</span></div>
|
||||
<strong>${tl("XRP Balance:")}</strong>
|
||||
${Number(data2.balance).toLocaleString(current_locale)} XRP
|
||||
</div>
|
||||
</div>`)
|
||||
|
||||
// TODO: Automatically populate all examples in the page with the
|
||||
// generated credentials...
|
||||
// $("code span:contains('"+EXAMPLE_ADDR+"')").each( function() {
|
||||
// let eltext = $(this).text()
|
||||
// $(this).text( eltext.replace(EXAMPLE_ADDR, data.account.address) )
|
||||
// })
|
||||
// $("code span:contains('"+EXAMPLE_SECRET+"')").each( function() {
|
||||
// let eltext = $(this).text()
|
||||
// $(this).text( eltext.replace(EXAMPLE_SECRET, data.account.secret) )
|
||||
// })
|
||||
//
|
||||
// block.find(".output-area").append(`<p>${tl("Populated this page's examples with these credentials.")}</p>`)
|
||||
|
||||
complete_step("Generate")
|
||||
|
||||
} catch(err) {
|
||||
block.find(".loader").hide()
|
||||
block.find(".output-area").html(
|
||||
`<p class="devportal-callout warning"><strong>${tl("Error:")}</strong>
|
||||
${tl("There was an error connecting to the Faucet. Please try again.")}
|
||||
</p>`)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function get_address_2(event, which_one) {
|
||||
// which_one should be either "cold" or "hot" (case-sensitive)
|
||||
const address = $(`#${which_one}-use-address`).text()
|
||||
if (!address) {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
if (!block.length) {return}
|
||||
show_error(block, tl("Couldn't get a valid address/secret value. Check that the previous steps were completed successfully."))
|
||||
}
|
||||
return address
|
||||
}
|
||||
function get_secret_2(event, which_one) {
|
||||
// which_one should be either "cold" or "hot" (case-sensitive)
|
||||
const secret = $(`#${which_one}-use-secret`).text()
|
||||
if (!secret) {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
if (!block.length) {return}
|
||||
show_error(block, tl("Couldn't get a valid address/secret value. Check that the previous steps were completed successfully."))
|
||||
}
|
||||
// TODO: check for *both* example secrets
|
||||
// if (secret == EXAMPLE_SECRET) {
|
||||
// const block = $(event.target).closest(".interactive-block")
|
||||
// if (!block.length) {return}
|
||||
// show_error(block, tl("Can't use the example secret here. Check that the previous steps were completed successfully."))
|
||||
// }
|
||||
return secret
|
||||
}
|
||||
|
||||
// Get the hexadecimal ASCII representation of a domain name string.
|
||||
// Note: if the provided string isn't compatible with 7-bit ASCII, this won't
|
||||
// work. So if you want to use an IDN, you'd need to convert to punycode first.
|
||||
function domain_to_hex(s) {
|
||||
result = ""
|
||||
for (let i=0; i<s.length; i++) {
|
||||
result += s.charCodeAt(i).toString(16)
|
||||
}
|
||||
return result.toUpperCase()
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
setup_2x_generate_step()
|
||||
|
||||
$("#cold-domain-text").keyup( (event) => {
|
||||
$("#cold-domain-hex").text(domain_to_hex($("#cold-domain-text").val()))
|
||||
})
|
||||
$("#hot-domain-text").keyup( (event) => {
|
||||
$("#hot-domain-hex").text(domain_to_hex($("#hot-domain-text").val()))
|
||||
})
|
||||
|
||||
function update_currency_code(event) {
|
||||
let currency_code
|
||||
if ($("#use-std-code").prop("checked")) {
|
||||
const std_code = $("#currency-code-std")
|
||||
currency_code = std_code.val().trim()
|
||||
// std_code.prop("disabled", false).removeClass("disabled")
|
||||
// $("#currency-code-hex").prop("disabled", true).addClass("disabled")
|
||||
|
||||
} else {
|
||||
const hex_code = $("#currency-code-hex")
|
||||
currency_code = hex_code.val().trim()
|
||||
// hex_code.prop("disabled", false).removeClass("disabled")
|
||||
// $("#currency-code-std").prop("disabled", true).addClass("disabled")
|
||||
}
|
||||
$("#send-currency-code").text(currency_code)
|
||||
}
|
||||
$("#currency-code-std").keyup(update_currency_code)
|
||||
$("#currency-code-hex").keyup(update_currency_code)
|
||||
$("#use-std-code").change(update_currency_code)
|
||||
$("#use-hex-code").change(update_currency_code)
|
||||
// run once on load because some browsers pre-fill values from previous
|
||||
// pageviews.
|
||||
update_currency_code()
|
||||
|
||||
|
||||
// Configure Issuer Settings handler -----------------------------------------
|
||||
$("#config-issuer-button").click( async (event) => {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
block.find(".output-area").empty()
|
||||
const cold_address = get_address_2(event, "cold")
|
||||
const cold_secret = get_secret_2(event, "cold")
|
||||
|
||||
let flags = 0
|
||||
if ($("#cold-require-dest").prop("checked")) {
|
||||
flags |= api.txFlags.AccountSet.RequireDestTag
|
||||
}
|
||||
if ($("#cold-disallow-xrp").prop("checked")) {
|
||||
flags |= api.txFlags.AccountSet.DisallowXRP
|
||||
}
|
||||
|
||||
const tick_size = parseInt($("#cold-tick-size").val(), 10)
|
||||
if (Number.isNaN(tick_size) || tick_size < 0 || tick_size > 15) {
|
||||
show_error(block, "TickSize must be an integer from 0 to 15.")
|
||||
return
|
||||
}
|
||||
|
||||
// Convert transfer fee % to transferrate integer (e.g. 0.5% fee = 1005000000)
|
||||
const transfer_fee = parseFloat($("#cold-transfer-fee").val())
|
||||
let transfer_rate = (transfer_fee * 10000000) + 1000000000
|
||||
if (transfer_rate == 1000000000) {
|
||||
transfer_rate = 0
|
||||
}
|
||||
|
||||
const domain = $("#cold-domain-hex").text().trim()
|
||||
|
||||
block.find(".loader").show()
|
||||
try {
|
||||
const cold_settings_tx = {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": cold_address,
|
||||
"TransferRate": transfer_rate,
|
||||
"TickSize": tick_size,
|
||||
"SetFlag": 8, // enable Default Ripple
|
||||
"Domain": domain,
|
||||
"Flags": flags
|
||||
}
|
||||
|
||||
await generic_full_send(event, cold_settings_tx, cold_secret)
|
||||
complete_step("Configure Issuer")
|
||||
|
||||
} catch(err) {
|
||||
block.find(".loader").hide()
|
||||
show_error(block, `An error occurred with the transaction: ${err}`)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// Configure Hot Address Settings handler ------------------------------------
|
||||
$("#config-hot-address-button").click( async (event) => {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
block.find(".output-area").empty()
|
||||
const hot_address = get_address_2(event, "hot")
|
||||
const hot_secret = get_secret_2(event, "hot")
|
||||
|
||||
let flags = 0
|
||||
if ($("#hot-require-dest").prop("checked")) {
|
||||
flags |= api.txFlags.AccountSet.RequireDestTag
|
||||
}
|
||||
if ($("#hot-disallow-xrp").prop("checked")) {
|
||||
flags |= api.txFlags.AccountSet.DisallowXRP
|
||||
}
|
||||
|
||||
const domain = $("#hot-domain-hex").text().trim()
|
||||
|
||||
block.find(".loader").show()
|
||||
try {
|
||||
const hot_settings_tx = {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": hot_address,
|
||||
"SetFlag": 2, // enable Require Auth so we can't accidentally issue from
|
||||
// the hot address
|
||||
"Domain": domain,
|
||||
"Flags": flags
|
||||
}
|
||||
|
||||
await generic_full_send(event, hot_settings_tx, hot_secret)
|
||||
complete_step("Configure Hot Address")
|
||||
|
||||
} catch(err) {
|
||||
block.find(".loader").hide()
|
||||
show_error(block, `An error occurred with the transaction: ${err}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Create Trust Line handler -------------------------------------------------
|
||||
$("#create-trust-line-button").click( async (event) => {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
block.find(".output-area").empty()
|
||||
const hot_address = get_address_2(event, "hot")
|
||||
const cold_address = get_address_2(event, "cold")
|
||||
const hot_secret = get_secret_2(event, "hot")
|
||||
|
||||
let currency_code
|
||||
if ($("#use-std-code").prop("checked")) {
|
||||
currency_code = $("#currency-code-std").val().trim()
|
||||
if (!currency_code.match(/[A-Za-z0-9?!@#$%*(){}|\x26\x3c\x3e]{3}/)) {
|
||||
show_error(block, "<a href='currency-formats.html#standard-currency-codes'>Standard currency code</a> must be 3 valid characters.")
|
||||
}
|
||||
} else {
|
||||
currency_code = $("#currency-code-hex").val().trim()
|
||||
if (!currency_code.match(/^[0-9A-Fa-f]{40}$/)) {
|
||||
show_error(block, "<a href='currency-formats.html#nonstandard-currency-codes'>Nonstandard currency code</a> must be 40 hexadecimal characters.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const limit = $("#trust-limit").val() // limit is a string
|
||||
|
||||
block.find(".loader").show()
|
||||
try {
|
||||
const trust_set_tx = {
|
||||
"TransactionType": "TrustSet",
|
||||
"Account": hot_address,
|
||||
"LimitAmount": {
|
||||
"currency": currency_code,
|
||||
"issuer": cold_address,
|
||||
"value": limit
|
||||
}
|
||||
}
|
||||
await generic_full_send(event, trust_set_tx, hot_secret)
|
||||
complete_step("Make Trust Line")
|
||||
|
||||
} catch(err) {
|
||||
block.find(".loader").hide()
|
||||
show_error(block, `An error occurred with the transaction: ${err}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Send Token handler --------------------------------------------------------
|
||||
$("#send-token-button").click( async (event) => {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
block.find(".output-area").empty()
|
||||
const hot_address = get_address_2(event, "hot")
|
||||
const cold_address = get_address_2(event, "cold")
|
||||
const cold_secret = get_secret_2(event, "cold")
|
||||
|
||||
const currency_code = $("#send-currency-code").text().trim()
|
||||
const issue_quantity = $("#send-amount").val().trim()
|
||||
|
||||
const use_dest_tag = $("#use-dest-tag").prop("checked")
|
||||
let dest_tag
|
||||
if (use_dest_tag) {
|
||||
dest_tag = parseInt($("#dest-tag").val(), 10)
|
||||
if (Number.isNaN(dest_tag) || dest_tag < 0 || dest_tag > 4294967295) {
|
||||
show_error(block, "Destination Tag must be a valid 32-bit integer.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
block.find(".loader").show()
|
||||
try {
|
||||
const send_token_tx = {
|
||||
"TransactionType": "Payment",
|
||||
"Account": cold_address,
|
||||
"Amount": {
|
||||
"currency": currency_code,
|
||||
"value": issue_quantity,
|
||||
"issuer": cold_address
|
||||
},
|
||||
"Destination": hot_address
|
||||
}
|
||||
if (use_dest_tag) {
|
||||
send_token_tx["DestinationTag"] = dest_tag
|
||||
}
|
||||
await generic_full_send(event, send_token_tx, cold_secret)
|
||||
complete_step("Send Token")
|
||||
|
||||
} catch(err) {
|
||||
block.find(".loader").hide()
|
||||
show_error(block, `An error occurred with the transaction: ${err}`)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// Confirm Balances handler --------------------------------------------------
|
||||
$("#confirm-balances-button").click( async (event) => {
|
||||
const block = $(event.target).closest(".interactive-block")
|
||||
block.find(".output-area").empty()
|
||||
const hot_address = get_address_2(event, "hot")
|
||||
const cold_address = get_address_2(event, "cold")
|
||||
|
||||
block.find(".loader").show()
|
||||
try {
|
||||
const hot_balances = await api.request("account_lines", {
|
||||
account: hot_address,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
block.find(".output-area").append(`
|
||||
<p>Hot address (<a href="https://testnet.xrpl.org/accounts/${hot_address}">${hot_address}</a>) account_lines result:</p>
|
||||
<pre><code>${pretty_print(hot_balances)}</code></pre>
|
||||
`)
|
||||
|
||||
const cold_balances = await api.request("gateway_balances", {
|
||||
account: cold_address,
|
||||
ledger_index: "validated",
|
||||
hotwallet: [hot_address]
|
||||
})
|
||||
block.find(".output-area").append(`
|
||||
<p>Issuer (<a href="https://testnet.xrpl.org/accounts/${cold_address}">${cold_address}</a>) gateway_balances result:</p>
|
||||
<pre><code>${pretty_print(cold_balances)}</code></pre>
|
||||
`)
|
||||
|
||||
block.find(".loader").hide()
|
||||
complete_step("Confirm Balances")
|
||||
} catch(err) {
|
||||
block.find(".loader").hide()
|
||||
show_error(block, `Error looking up balances: ${err}`)
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
@@ -4,16 +4,21 @@
|
||||
$(document).ready(() => {
|
||||
|
||||
// 3. Send AccountSet --------------------------------------------------------
|
||||
$("#send-accountset").click( (event) => {
|
||||
$("#send-accountset").click( async (event) => {
|
||||
const address = get_address(event)
|
||||
if (!address) {return}
|
||||
|
||||
generic_full_send(event, {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": address,
|
||||
"SetFlag": 1 // RequireDest
|
||||
})
|
||||
complete_step("Send AccountSet")
|
||||
try {
|
||||
await generic_full_send(event, {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": address,
|
||||
"SetFlag": 1 // RequireDest
|
||||
})
|
||||
complete_step("Send AccountSet")
|
||||
} catch(err) {
|
||||
block.find(".loader").hide()
|
||||
show_error(block, err)
|
||||
}
|
||||
})
|
||||
|
||||
// 4. Wait for Validation: handled by interactive-tutorial.js and by the
|
||||
@@ -112,7 +117,7 @@ $(document).ready(() => {
|
||||
target="_blank">${prelim_result.engine_result}</a></p>`)
|
||||
} catch(err) {
|
||||
block.find(".loader").hide()
|
||||
show_error(`An error occurred when sending the test payment: ${err}`)
|
||||
show_error(block, `An error occurred when sending the test payment: ${err}`)
|
||||
}
|
||||
|
||||
|
||||
|
||||
263
content/_code-samples/issue-a-token/java/IssueToken.java
Normal file
263
content/_code-samples/issue-a-token/java/IssueToken.java
Normal file
@@ -0,0 +1,263 @@
|
||||
// Stand-alone code sample for the "issue a token" tutorial:
|
||||
// https://xrpl.org/issue-a-fungible-token.html
|
||||
// License: https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.google.common.primitives.UnsignedLong;
|
||||
import okhttp3.HttpUrl;
|
||||
import org.xrpl.xrpl4j.client.JsonRpcClientErrorException;
|
||||
import org.xrpl.xrpl4j.client.XrplClient;
|
||||
import org.xrpl.xrpl4j.client.faucet.FaucetClient;
|
||||
import org.xrpl.xrpl4j.client.faucet.FundAccountRequest;
|
||||
import org.xrpl.xrpl4j.crypto.KeyMetadata;
|
||||
import org.xrpl.xrpl4j.crypto.PrivateKey;
|
||||
import org.xrpl.xrpl4j.crypto.signing.SignatureService;
|
||||
import org.xrpl.xrpl4j.crypto.signing.SignedTransaction;
|
||||
import org.xrpl.xrpl4j.crypto.signing.SingleKeySignatureService;
|
||||
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoRequestParams;
|
||||
import org.xrpl.xrpl4j.model.client.accounts.AccountLinesRequestParams;
|
||||
import org.xrpl.xrpl4j.model.client.accounts.TrustLine;
|
||||
import org.xrpl.xrpl4j.model.client.common.LedgerIndex;
|
||||
import org.xrpl.xrpl4j.model.client.fees.FeeResult;
|
||||
import org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams;
|
||||
import org.xrpl.xrpl4j.model.client.transactions.TransactionRequestParams;
|
||||
import org.xrpl.xrpl4j.model.client.transactions.TransactionResult;
|
||||
import org.xrpl.xrpl4j.model.immutables.FluentCompareTo;
|
||||
import org.xrpl.xrpl4j.model.transactions.AccountSet;
|
||||
import org.xrpl.xrpl4j.model.transactions.ImmutableTrustSet;
|
||||
import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount;
|
||||
import org.xrpl.xrpl4j.model.transactions.Payment;
|
||||
import org.xrpl.xrpl4j.model.transactions.TrustSet;
|
||||
import org.xrpl.xrpl4j.wallet.DefaultWalletFactory;
|
||||
import org.xrpl.xrpl4j.wallet.Wallet;
|
||||
import org.xrpl.xrpl4j.wallet.WalletFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class IssueToken {
|
||||
|
||||
public static void main(String[] args)
|
||||
throws InterruptedException, JsonRpcClientErrorException, JsonProcessingException {
|
||||
// Construct a network client ----------------------------------------------
|
||||
HttpUrl rippledUrl = HttpUrl
|
||||
.get("https://s.altnet.rippletest.net:51234/");
|
||||
XrplClient xrplClient = new XrplClient(rippledUrl);
|
||||
// Get the current network fee
|
||||
FeeResult feeResult = xrplClient.fee();
|
||||
|
||||
|
||||
// Create cold and hot Wallets using a WalletFactory -----------------------
|
||||
WalletFactory walletFactory = DefaultWalletFactory.getInstance();
|
||||
Wallet coldWallet = walletFactory.randomWallet(true).wallet();
|
||||
Wallet hotWallet = walletFactory.randomWallet(true).wallet();
|
||||
|
||||
// Fund the account using the testnet Faucet -------------------------------
|
||||
FaucetClient faucetClient = FaucetClient
|
||||
.construct(HttpUrl.get("https://faucet.altnet.rippletest.net"));
|
||||
faucetClient.fundAccount(FundAccountRequest.of(coldWallet.classicAddress()));
|
||||
faucetClient.fundAccount(FundAccountRequest.of(hotWallet.classicAddress()));
|
||||
|
||||
// If you go too soon, the funding transaction might slip back a ledger and
|
||||
// then your starting Sequence number will be off. This is mostly relevant
|
||||
// when you want to use a Testnet account right after getting a reply from
|
||||
// the faucet.
|
||||
boolean accountsFunded = false;
|
||||
while (!accountsFunded) {
|
||||
try {
|
||||
xrplClient.accountInfo(
|
||||
AccountInfoRequestParams.builder()
|
||||
.ledgerIndex(LedgerIndex.VALIDATED)
|
||||
.account(coldWallet.classicAddress())
|
||||
.build()
|
||||
);
|
||||
|
||||
xrplClient.accountInfo(
|
||||
AccountInfoRequestParams.builder()
|
||||
.ledgerIndex(LedgerIndex.VALIDATED)
|
||||
.account(hotWallet.classicAddress())
|
||||
.build()
|
||||
);
|
||||
|
||||
accountsFunded = true;
|
||||
} catch (JsonRpcClientErrorException e) {
|
||||
if (!e.getMessage().equals("Account not found.")) {
|
||||
throw e;
|
||||
}
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Configure issuer settings -----------------------------------------------
|
||||
UnsignedInteger coldWalletSequence = xrplClient.accountInfo(
|
||||
AccountInfoRequestParams.builder()
|
||||
.ledgerIndex(LedgerIndex.CURRENT)
|
||||
.account(coldWallet.classicAddress())
|
||||
.build()
|
||||
).accountData().sequence();
|
||||
|
||||
AccountSet setDefaultRipple = AccountSet.builder()
|
||||
.account(coldWallet.classicAddress())
|
||||
.fee(feeResult.drops().minimumFee())
|
||||
.sequence(coldWalletSequence)
|
||||
.signingPublicKey(coldWallet.publicKey())
|
||||
.setFlag(AccountSet.AccountSetFlag.DEFAULT_RIPPLE)
|
||||
.lastLedgerSequence(computeLastLedgerSequence(xrplClient))
|
||||
.build();
|
||||
|
||||
PrivateKey coldWalletPrivateKey = PrivateKey.fromBase16EncodedPrivateKey(
|
||||
coldWallet.privateKey().get()
|
||||
);
|
||||
SignatureService coldWalletSignatureService = new SingleKeySignatureService(coldWalletPrivateKey);
|
||||
|
||||
SignedTransaction<AccountSet> signedSetDefaultRipple = coldWalletSignatureService.sign(
|
||||
KeyMetadata.EMPTY,
|
||||
setDefaultRipple
|
||||
);
|
||||
|
||||
submitAndWaitForValidation(signedSetDefaultRipple, xrplClient);
|
||||
|
||||
// Configure hot address settings ------------------------------------------
|
||||
UnsignedInteger hotWalletSequence = xrplClient.accountInfo(
|
||||
AccountInfoRequestParams.builder()
|
||||
.ledgerIndex(LedgerIndex.CURRENT)
|
||||
.account(hotWallet.classicAddress())
|
||||
.build()
|
||||
).accountData().sequence();
|
||||
|
||||
AccountSet setRequireAuth = AccountSet.builder()
|
||||
.account(hotWallet.classicAddress())
|
||||
.fee(feeResult.drops().minimumFee())
|
||||
.sequence(hotWalletSequence)
|
||||
.signingPublicKey(hotWallet.publicKey())
|
||||
.setFlag(AccountSet.AccountSetFlag.REQUIRE_AUTH)
|
||||
.lastLedgerSequence(computeLastLedgerSequence(xrplClient))
|
||||
.build();
|
||||
|
||||
PrivateKey hotWalletPrivateKey = PrivateKey.fromBase16EncodedPrivateKey(
|
||||
hotWallet.privateKey().get()
|
||||
);
|
||||
SignatureService hotWalletSignatureService = new SingleKeySignatureService(hotWalletPrivateKey);
|
||||
|
||||
SignedTransaction<AccountSet> signedSetRequireAuth = hotWalletSignatureService.sign(
|
||||
KeyMetadata.EMPTY,
|
||||
setRequireAuth
|
||||
);
|
||||
|
||||
submitAndWaitForValidation(signedSetRequireAuth, xrplClient);
|
||||
|
||||
// Create trust line -------------------------------------------------------
|
||||
String currencyCode = "FOO";
|
||||
ImmutableTrustSet trustSet = TrustSet.builder()
|
||||
.account(hotWallet.classicAddress())
|
||||
.fee(feeResult.drops().openLedgerFee())
|
||||
.sequence(hotWalletSequence.plus(UnsignedInteger.ONE))
|
||||
.limitAmount(IssuedCurrencyAmount.builder()
|
||||
.currency(currencyCode)
|
||||
.issuer(coldWallet.classicAddress())
|
||||
.value("10000000000")
|
||||
.build())
|
||||
.signingPublicKey(hotWallet.publicKey())
|
||||
.build();
|
||||
|
||||
SignedTransaction<TrustSet> signedTrustSet = hotWalletSignatureService.sign(
|
||||
KeyMetadata.EMPTY,
|
||||
trustSet
|
||||
);
|
||||
|
||||
submitAndWaitForValidation(signedTrustSet, xrplClient);
|
||||
|
||||
// Send token --------------------------------------------------------------
|
||||
Payment payment = Payment.builder()
|
||||
.account(coldWallet.classicAddress())
|
||||
.fee(feeResult.drops().openLedgerFee())
|
||||
.sequence(coldWalletSequence.plus(UnsignedInteger.ONE))
|
||||
.destination(hotWallet.classicAddress())
|
||||
.amount(IssuedCurrencyAmount.builder()
|
||||
.issuer(coldWallet.classicAddress())
|
||||
.currency(currencyCode)
|
||||
.value("3840")
|
||||
.build())
|
||||
.signingPublicKey(coldWallet.publicKey())
|
||||
.build();
|
||||
|
||||
SignedTransaction<Payment> signedPayment = coldWalletSignatureService.sign(
|
||||
KeyMetadata.EMPTY,
|
||||
payment
|
||||
);
|
||||
|
||||
submitAndWaitForValidation(signedPayment, xrplClient);
|
||||
|
||||
// Check balances ----------------------------------------------------------
|
||||
List<TrustLine> lines = xrplClient.accountLines(
|
||||
AccountLinesRequestParams.builder()
|
||||
.account(hotWallet.classicAddress())
|
||||
.ledgerIndex(LedgerIndex.VALIDATED)
|
||||
.build()
|
||||
).lines();
|
||||
System.out.println("Hot wallet TrustLines: " + lines);
|
||||
}
|
||||
|
||||
// Helper methods ------------------------------------------------------------
|
||||
private static UnsignedInteger computeLastLedgerSequence(XrplClient xrplClient)
|
||||
throws JsonRpcClientErrorException {
|
||||
// Get the latest validated ledger index
|
||||
LedgerIndex validatedLedger = xrplClient.ledger(
|
||||
LedgerRequestParams.builder()
|
||||
.ledgerIndex(LedgerIndex.VALIDATED)
|
||||
.build()
|
||||
)
|
||||
.ledgerIndex()
|
||||
.orElseThrow(() -> new RuntimeException("LedgerIndex not available."));
|
||||
|
||||
// Workaround for https://github.com/XRPLF/xrpl4j/issues/84
|
||||
return UnsignedInteger.valueOf(
|
||||
validatedLedger.plus(UnsignedLong.valueOf(4)).unsignedLongValue().intValue()
|
||||
);
|
||||
}
|
||||
|
||||
private static void submitAndWaitForValidation(SignedTransaction<?> signedTransaction, XrplClient xrplClient)
|
||||
throws InterruptedException, JsonRpcClientErrorException, JsonProcessingException {
|
||||
|
||||
xrplClient.submit(signedTransaction);
|
||||
|
||||
boolean transactionValidated = false;
|
||||
boolean transactionExpired = false;
|
||||
while (!transactionValidated && !transactionExpired) {
|
||||
Thread.sleep(4 * 1000);
|
||||
LedgerIndex latestValidatedLedgerIndex = xrplClient.ledger(
|
||||
LedgerRequestParams.builder().ledgerIndex(LedgerIndex.VALIDATED).build()
|
||||
)
|
||||
.ledgerIndex()
|
||||
.orElseThrow(() ->
|
||||
new RuntimeException("Ledger response did not contain a LedgerIndex.")
|
||||
);
|
||||
|
||||
TransactionResult<Payment> transactionResult = xrplClient.transaction(
|
||||
TransactionRequestParams.of(signedTransaction.hash()),
|
||||
Payment.class
|
||||
);
|
||||
|
||||
if (transactionResult.validated()) {
|
||||
System.out.println("Transaction was validated with result code " +
|
||||
transactionResult.metadata().get().transactionResult());
|
||||
transactionValidated = true;
|
||||
} else {
|
||||
boolean lastLedgerSequenceHasPassed = FluentCompareTo.
|
||||
is(latestValidatedLedgerIndex.unsignedLongValue())
|
||||
.greaterThan(UnsignedLong.valueOf(
|
||||
signedTransaction.signedTransaction().lastLedgerSequence().get().intValue()
|
||||
)
|
||||
);
|
||||
if (lastLedgerSequenceHasPassed) {
|
||||
System.out.println("LastLedgerSequence has passed. Last tx response: " +
|
||||
transactionResult);
|
||||
transactionExpired = true;
|
||||
} else {
|
||||
System.out.println("Transaction not yet validated.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
5
content/_code-samples/issue-a-token/java/README.md
Normal file
5
content/_code-samples/issue-a-token/java/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Issue a Token (Java sample code)
|
||||
|
||||
This code demonstrates how to issue a (fungible) token on the XRP Ledger. For a detailed explanation, see <https://xrpl.org/issue-a-fungible-token.html>.
|
||||
|
||||
The easiest way to run this code is from an IDE such as IntelliJ. For an example `pom.xml` file, see [Get Started Using Java](https://xrpl.org/get-started-using-java.html).
|
||||
7
content/_code-samples/issue-a-token/js/README.md
Normal file
7
content/_code-samples/issue-a-token/js/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Issue a Token Sample Code (JavaScript)
|
||||
|
||||
This code demonstrates how to issue a (fungible) token on the XRP Ledger. For a detailed explanation, see <https://xrpl.org/issue-a-fungible-token.html>.
|
||||
|
||||
The code is designed to run in-browser by loading `demo.html` and watching the console output or in Node.js. For Node.js, you must first install the dependencies using your preferred package manager (such as `yarn` or `npm`).
|
||||
|
||||
In both cases you also need [the `submit-and-verify.js` code from another folder of this repository](../submit-and-verify/). It should already be in the right place if you cloned or download an archive of the repo.
|
||||
11
content/_code-samples/issue-a-token/js/demo.html
Normal file
11
content/_code-samples/issue-a-token/js/demo.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Code Sample - Issue a Token</title>
|
||||
<script src="https://unpkg.com/ripple-lib@1.10.0/build/ripple-latest-min.js"></script>
|
||||
<script type="application/javascript" src="../../submit-and-verify/submit-and-verify.js"></script>
|
||||
<script type="application/javascript" src="issue-a-token.js"></script>
|
||||
</head>
|
||||
<body>Open your browser's console (F12) to see the logs.</body>
|
||||
</html>
|
||||
177
content/_code-samples/issue-a-token/js/issue-a-token.js
Normal file
177
content/_code-samples/issue-a-token/js/issue-a-token.js
Normal file
@@ -0,0 +1,177 @@
|
||||
// Stand-alone code sample for the "issue a token" tutorial:
|
||||
// https://xrpl.org/issue-a-fungible-token.html
|
||||
|
||||
// Dependencies for Node.js.
|
||||
// In browsers, use <script> tags as in the example demo.html.
|
||||
if (typeof module !== "undefined") {
|
||||
// gotta use var here because const/let are block-scoped to the if statement.
|
||||
var ripple = require('ripple-lib')
|
||||
var submit_and_verify = require('../../submit-and-verify/submit-and-verify.js').submit_and_verify
|
||||
}
|
||||
|
||||
// Connect ---------------------------------------------------------------------
|
||||
async function main() {
|
||||
api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||
console.log("Connecting to Testnet...")
|
||||
await api.connect()
|
||||
|
||||
// Get credentials from the Testnet Faucet -----------------------------------
|
||||
console.log("Requesting addresses from the Testnet faucet...")
|
||||
const hot_data = await api.generateFaucetWallet()
|
||||
const hot_address = hot_data.account.classicAddress
|
||||
const hot_secret = hot_data.account.secret
|
||||
|
||||
const cold_data = await api.generateFaucetWallet()
|
||||
const cold_address = cold_data.account.classicAddress
|
||||
const cold_secret = cold_data.account.secret
|
||||
|
||||
console.log("Waiting until we have a validated starting sequence number...")
|
||||
// If you go too soon, the funding transaction might slip back a ledger and
|
||||
// then your starting Sequence number will be off. This is mostly relevant
|
||||
// when you want to use a Testnet account right after getting a reply from
|
||||
// the faucet.
|
||||
while (true) {
|
||||
try {
|
||||
await api.request("account_info", {
|
||||
account: cold_address,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
await api.request("account_info", {
|
||||
account: hot_address,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
break
|
||||
} catch(e) {
|
||||
if (e.data.error != 'actNotFound') throw e
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
}
|
||||
console.log(`Got hot address ${hot_address} and cold address ${cold_address}.`)
|
||||
|
||||
// Configure issuer (cold address) settings ----------------------------------
|
||||
const cold_settings_tx = {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": cold_address,
|
||||
"TransferRate": 0,
|
||||
"TickSize": 5,
|
||||
"Domain": "6578616D706C652E636F6D", // "example.com"
|
||||
"SetFlag": 8 // enable Default Ripple
|
||||
//"Flags": (api.txFlags.AccountSet.DisallowXRP |
|
||||
// api.txFlags.AccountSet.RequireDestTag)
|
||||
}
|
||||
|
||||
const cst_prepared = await api.prepareTransaction(
|
||||
cold_settings_tx,
|
||||
{maxLedgerVersionOffset: 10}
|
||||
)
|
||||
const cst_signed = api.sign(cst_prepared.txJSON, cold_secret)
|
||||
// submit_and_verify helper function from:
|
||||
// https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/submit-and-verify/
|
||||
console.log("Sending cold address AccountSet transaction...")
|
||||
const cst_result = await submit_and_verify(api, cst_signed.signedTransaction)
|
||||
if (cst_result == "tesSUCCESS") {
|
||||
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${cst_signed.id}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${cst_result}`
|
||||
}
|
||||
|
||||
|
||||
// Configure hot address settings --------------------------------------------
|
||||
|
||||
const hot_settings_tx = {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": hot_address,
|
||||
"Domain": "6578616D706C652E636F6D", // "example.com"
|
||||
"SetFlag": 2 // enable Require Auth so we can't use trust lines that users
|
||||
// make to the hot address, even by accident.
|
||||
//"Flags": (api.txFlags.AccountSet.DisallowXRP |
|
||||
// api.txFlags.AccountSet.RequireDestTag)
|
||||
}
|
||||
|
||||
const hst_prepared = await api.prepareTransaction(
|
||||
hot_settings_tx,
|
||||
{maxLedgerVersionOffset: 10}
|
||||
)
|
||||
const hst_signed = api.sign(hst_prepared.txJSON, hot_secret)
|
||||
console.log("Sending hot address AccountSet transaction...")
|
||||
const hst_result = await submit_and_verify(api, hst_signed.signedTransaction)
|
||||
if (hst_result == "tesSUCCESS") {
|
||||
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${hst_signed.id}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${hst_result}`
|
||||
}
|
||||
|
||||
|
||||
// Create trust line from hot to cold address --------------------------------
|
||||
const currency_code = "FOO"
|
||||
const trust_set_tx = {
|
||||
"TransactionType": "TrustSet",
|
||||
"Account": hot_address,
|
||||
"LimitAmount": {
|
||||
"currency": currency_code,
|
||||
"issuer": cold_address,
|
||||
"value": "10000000000" // Large limit, arbitrarily chosen
|
||||
}
|
||||
}
|
||||
|
||||
const ts_prepared = await api.prepareTransaction(
|
||||
trust_set_tx,
|
||||
{maxLedgerVersionOffset: 10}
|
||||
)
|
||||
const ts_signed = api.sign(ts_prepared.txJSON, hot_secret)
|
||||
console.log("Creating trust line from hot address to issuer...")
|
||||
const ts_result = await submit_and_verify(api, ts_signed.signedTransaction)
|
||||
if (ts_result == "tesSUCCESS") {
|
||||
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${ts_signed.id}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${ts_result}`
|
||||
}
|
||||
|
||||
|
||||
// Send token ----------------------------------------------------------------
|
||||
const issue_quantity = "3840"
|
||||
const send_token_tx = {
|
||||
"TransactionType": "Payment",
|
||||
"Account": cold_address,
|
||||
"Amount": {
|
||||
"currency": currency_code,
|
||||
"value": issue_quantity,
|
||||
"issuer": cold_address
|
||||
},
|
||||
"Destination": hot_address
|
||||
}
|
||||
|
||||
const pay_prepared = await api.prepareTransaction(
|
||||
send_token_tx,
|
||||
{maxLedgerVersionOffset: 10}
|
||||
)
|
||||
const pay_signed = api.sign(pay_prepared.txJSON, cold_secret)
|
||||
// submit_and_verify helper from _code-samples/submit-and-verify
|
||||
console.log(`Sending ${issue_quantity} ${currency_code} to ${hot_address}...`)
|
||||
const pay_result = await submit_and_verify(api, pay_signed.signedTransaction)
|
||||
if (pay_result == "tesSUCCESS") {
|
||||
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${pay_signed.id}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${pay_result}`
|
||||
}
|
||||
|
||||
// Check balances ------------------------------------------------------------
|
||||
console.log("Getting hot address balances...")
|
||||
const hot_balances = await api.request("account_lines", {
|
||||
account: hot_address,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
console.log(hot_balances)
|
||||
|
||||
console.log("Getting cold address balances...")
|
||||
const cold_balances = await api.request("gateway_balances", {
|
||||
account: cold_address,
|
||||
ledger_index: "validated",
|
||||
hotwallet: [hot_address]
|
||||
})
|
||||
console.log(JSON.stringify(cold_balances, null, 2))
|
||||
|
||||
api.disconnect()
|
||||
} // End of main()
|
||||
|
||||
main()
|
||||
5
content/_code-samples/issue-a-token/js/package.json
Normal file
5
content/_code-samples/issue-a-token/js/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"ripple-lib": "^1.10.0"
|
||||
}
|
||||
}
|
||||
612
content/_code-samples/issue-a-token/js/yarn.lock
Normal file
612
content/_code-samples/issue-a-token/js/yarn.lock
Normal file
@@ -0,0 +1,612 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/lodash@^4.14.136":
|
||||
version "4.14.172"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
|
||||
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
|
||||
|
||||
"@types/node@*":
|
||||
version "16.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.4.14.tgz#e27705ec2278b2355bd59f1952de23a152b9f208"
|
||||
integrity sha512-GZpnVRNtv7sHDXIFncsERt+qvj4rzAgRQtnvzk3Z7OVNtThD2dHXYCMDNc80D5mv4JE278qo8biZCwcmkbdpqw==
|
||||
|
||||
"@types/ws@^7.2.0":
|
||||
version "7.4.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702"
|
||||
integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
agent-base@6:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
||||
dependencies:
|
||||
debug "4"
|
||||
|
||||
assert@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32"
|
||||
integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==
|
||||
dependencies:
|
||||
es6-object-assign "^1.1.0"
|
||||
is-nan "^1.2.1"
|
||||
object-is "^1.0.1"
|
||||
util "^0.12.0"
|
||||
|
||||
available-typed-arrays@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9"
|
||||
integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==
|
||||
|
||||
base-x@3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d"
|
||||
integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
base64-js@^1.0.2:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
big-integer@^1.6.48:
|
||||
version "1.6.48"
|
||||
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
|
||||
integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==
|
||||
|
||||
bignumber.js@^9.0.0:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5"
|
||||
integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==
|
||||
|
||||
bn.js@^4.11.9:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
|
||||
bn.js@^5.1.1:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
|
||||
integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
|
||||
|
||||
brorand@^1.0.5, brorand@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
|
||||
|
||||
buffer@5.6.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
||||
integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
call-bind@^1.0.0, call-bind@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
get-intrinsic "^1.0.2"
|
||||
|
||||
cipher-base@^1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
|
||||
integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==
|
||||
dependencies:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
create-hash@^1.1.2, create-hash@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
||||
integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
|
||||
dependencies:
|
||||
cipher-base "^1.0.1"
|
||||
inherits "^2.0.1"
|
||||
md5.js "^1.3.4"
|
||||
ripemd160 "^2.0.1"
|
||||
sha.js "^2.4.0"
|
||||
|
||||
debug@4:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
decimal.js@^10.2.0:
|
||||
version "10.3.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
|
||||
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
|
||||
|
||||
define-properties@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
|
||||
dependencies:
|
||||
object-keys "^1.0.12"
|
||||
|
||||
elliptic@^6.5.2:
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
dependencies:
|
||||
bn.js "^4.11.9"
|
||||
brorand "^1.1.0"
|
||||
hash.js "^1.0.0"
|
||||
hmac-drbg "^1.0.1"
|
||||
inherits "^2.0.4"
|
||||
minimalistic-assert "^1.0.1"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
es-abstract@^1.18.5:
|
||||
version "1.18.5"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19"
|
||||
integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
es-to-primitive "^1.2.1"
|
||||
function-bind "^1.1.1"
|
||||
get-intrinsic "^1.1.1"
|
||||
has "^1.0.3"
|
||||
has-symbols "^1.0.2"
|
||||
internal-slot "^1.0.3"
|
||||
is-callable "^1.2.3"
|
||||
is-negative-zero "^2.0.1"
|
||||
is-regex "^1.1.3"
|
||||
is-string "^1.0.6"
|
||||
object-inspect "^1.11.0"
|
||||
object-keys "^1.1.1"
|
||||
object.assign "^4.1.2"
|
||||
string.prototype.trimend "^1.0.4"
|
||||
string.prototype.trimstart "^1.0.4"
|
||||
unbox-primitive "^1.0.1"
|
||||
|
||||
es-to-primitive@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
|
||||
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
|
||||
dependencies:
|
||||
is-callable "^1.1.4"
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.2"
|
||||
|
||||
es6-object-assign@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c"
|
||||
integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=
|
||||
|
||||
foreach@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
|
||||
integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
|
||||
get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
|
||||
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
has-symbols "^1.0.1"
|
||||
|
||||
has-bigints@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
|
||||
integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
|
||||
|
||||
has-symbols@^1.0.1, has-symbols@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
|
||||
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
|
||||
|
||||
has-tostringtag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
|
||||
integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
|
||||
dependencies:
|
||||
has-symbols "^1.0.2"
|
||||
|
||||
has@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hash-base@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
|
||||
integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==
|
||||
dependencies:
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.6.0"
|
||||
safe-buffer "^5.2.0"
|
||||
|
||||
hash.js@^1.0.0, hash.js@^1.0.3:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
|
||||
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
minimalistic-assert "^1.0.1"
|
||||
|
||||
hmac-drbg@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
|
||||
dependencies:
|
||||
hash.js "^1.0.3"
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
https-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
|
||||
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
|
||||
dependencies:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
||||
inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
internal-slot@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
|
||||
integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.0"
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
is-arguments@^1.0.4:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
|
||||
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-bigint@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.3.tgz#fc9d9e364210480675653ddaea0518528d49a581"
|
||||
integrity sha512-ZU538ajmYJmzysE5yU4Y7uIrPQ2j704u+hXFiIPQExpqzzUbpe5jCPdTfmz7jXRxZdvjY3KZ3ZNenoXQovX+Dg==
|
||||
|
||||
is-boolean-object@^1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
|
||||
integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-callable@^1.1.4, is-callable@^1.2.3:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945"
|
||||
integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==
|
||||
|
||||
is-date-object@^1.0.1:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
|
||||
integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-generator-function@^1.0.7:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
|
||||
integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-nan@^1.2.1:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
|
||||
integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
define-properties "^1.1.3"
|
||||
|
||||
is-negative-zero@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
|
||||
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
|
||||
|
||||
is-number-object@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0"
|
||||
integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-regex@^1.1.3:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
|
||||
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-string@^1.0.5, is-string@^1.0.6:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
|
||||
integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
|
||||
dependencies:
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
is-symbol@^1.0.2, is-symbol@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
|
||||
integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
|
||||
dependencies:
|
||||
has-symbols "^1.0.2"
|
||||
|
||||
is-typed-array@^1.1.3, is-typed-array@^1.1.6:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.7.tgz#881ddc660b13cb8423b2090fa88c0fe37a83eb2f"
|
||||
integrity sha512-VxlpTBGknhQ3o7YiVjIhdLU6+oD8dPz/79vvvH4F+S/c8608UCVa9fgDpa1kZgFoUST2DCgacc70UszKgzKuvA==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.4"
|
||||
call-bind "^1.0.2"
|
||||
es-abstract "^1.18.5"
|
||||
foreach "^2.0.5"
|
||||
has-tostringtag "^1.0.0"
|
||||
|
||||
jsonschema@1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.2.tgz#83ab9c63d65bf4d596f91d81195e78772f6452bc"
|
||||
integrity sha512-iX5OFQ6yx9NgbHCwse51ohhKgLuLL7Z5cNOeZOPIlDUtAMrxlruHLzVZxbltdHE5mEDXN+75oFOwq6Gn0MZwsA==
|
||||
|
||||
lodash@^4.17.15, lodash@^4.17.4:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
md5.js@^1.3.4:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||
integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
|
||||
dependencies:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimalistic-crypto-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
object-inspect@^1.11.0, object-inspect@^1.9.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
|
||||
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
|
||||
|
||||
object-is@^1.0.1:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
|
||||
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
|
||||
object-keys@^1.0.12, object-keys@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
|
||||
|
||||
object.assign@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
|
||||
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
define-properties "^1.1.3"
|
||||
has-symbols "^1.0.1"
|
||||
object-keys "^1.1.1"
|
||||
|
||||
readable-stream@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
ripemd160@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
|
||||
integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
|
||||
dependencies:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
ripple-address-codec@^4.0.0, ripple-address-codec@^4.1.1:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.1.3.tgz#5437e2b7ddfdc7cfd4eb610fc4cf5ece15af949a"
|
||||
integrity sha512-9mymOhfCUyLZGwotGPg3I2wMfrwHof0W8ygjhW46UdNgFW6J+OvDB/VS9dbHlgED/41mzECp41IXvTrkc1fTwA==
|
||||
dependencies:
|
||||
base-x "3.0.8"
|
||||
create-hash "^1.1.2"
|
||||
|
||||
ripple-binary-codec@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-1.1.3.tgz#9dc6cd139fd587ec6fc2ffe72fc1f0ced53ca906"
|
||||
integrity sha512-NnFNZZ+225BxdDdHtcEn4GiGzup+V0DGAbtKygZIwbqA5116oZBt6uY3g43gYpdDMISsEbM7NewBij8+7jdlvA==
|
||||
dependencies:
|
||||
assert "^2.0.0"
|
||||
big-integer "^1.6.48"
|
||||
buffer "5.6.0"
|
||||
create-hash "^1.2.0"
|
||||
decimal.js "^10.2.0"
|
||||
ripple-address-codec "^4.1.1"
|
||||
|
||||
ripple-keypairs@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/ripple-keypairs/-/ripple-keypairs-1.0.3.tgz#346f15fa25e020e0afaa7f6e31fe398e119344b2"
|
||||
integrity sha512-Na5q8sUdxjd5DXBM88ocJgL2Ig0I1USyO3bvI0SMxJPp7F9DHvqLdPX45PVXs7HUq0Dj691Z9Uz9NeD/K7/eOA==
|
||||
dependencies:
|
||||
bn.js "^5.1.1"
|
||||
brorand "^1.0.5"
|
||||
elliptic "^6.5.2"
|
||||
hash.js "^1.0.3"
|
||||
ripple-address-codec "^4.0.0"
|
||||
|
||||
ripple-lib-transactionparser@0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.8.2.tgz#7aaad3ba1e1aeee1d5bcff32334a7a838f834dce"
|
||||
integrity sha512-1teosQLjYHLyOQrKUQfYyMjDR3MAq/Ga+MJuLUfpBMypl4LZB4bEoMcmG99/+WVTEiZOezJmH9iCSvm/MyxD+g==
|
||||
dependencies:
|
||||
bignumber.js "^9.0.0"
|
||||
lodash "^4.17.15"
|
||||
|
||||
ripple-lib@^1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/ripple-lib/-/ripple-lib-1.10.0.tgz#e41aaf17d5c6e6f8bcc8116736ac108ff3d6b810"
|
||||
integrity sha512-Cg2u73UybfM1PnzcuLt5flvLKZn35ovdIp+1eLrReVB4swuRuUF/SskJG9hf5wMosbvh+E+jZu8A6IbYJoyFIA==
|
||||
dependencies:
|
||||
"@types/lodash" "^4.14.136"
|
||||
"@types/ws" "^7.2.0"
|
||||
bignumber.js "^9.0.0"
|
||||
https-proxy-agent "^5.0.0"
|
||||
jsonschema "1.2.2"
|
||||
lodash "^4.17.4"
|
||||
ripple-address-codec "^4.1.1"
|
||||
ripple-binary-codec "^1.1.3"
|
||||
ripple-keypairs "^1.0.3"
|
||||
ripple-lib-transactionparser "0.8.2"
|
||||
ws "^7.2.0"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
sha.js@^2.4.0:
|
||||
version "2.4.11"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
|
||||
dependencies:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
|
||||
dependencies:
|
||||
call-bind "^1.0.0"
|
||||
get-intrinsic "^1.0.2"
|
||||
object-inspect "^1.9.0"
|
||||
|
||||
string.prototype.trimend@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
|
||||
integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
|
||||
string.prototype.trimstart@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
|
||||
integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
unbox-primitive@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
|
||||
integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
has-bigints "^1.0.1"
|
||||
has-symbols "^1.0.2"
|
||||
which-boxed-primitive "^1.0.2"
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
util@^0.12.0:
|
||||
version "0.12.4"
|
||||
resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253"
|
||||
integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
is-arguments "^1.0.4"
|
||||
is-generator-function "^1.0.7"
|
||||
is-typed-array "^1.1.3"
|
||||
safe-buffer "^5.1.2"
|
||||
which-typed-array "^1.1.2"
|
||||
|
||||
which-boxed-primitive@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||
integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
|
||||
dependencies:
|
||||
is-bigint "^1.0.1"
|
||||
is-boolean-object "^1.1.0"
|
||||
is-number-object "^1.0.4"
|
||||
is-string "^1.0.5"
|
||||
is-symbol "^1.0.3"
|
||||
|
||||
which-typed-array@^1.1.2:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.6.tgz#f3713d801da0720a7f26f50c596980a9f5c8b383"
|
||||
integrity sha512-DdY984dGD5sQ7Tf+x1CkXzdg85b9uEel6nr4UkFg1LoE9OXv3uRuZhe5CoWdawhGACeFpEZXH8fFLQnDhbpm/Q==
|
||||
dependencies:
|
||||
available-typed-arrays "^1.0.4"
|
||||
call-bind "^1.0.2"
|
||||
es-abstract "^1.18.5"
|
||||
foreach "^2.0.5"
|
||||
has-tostringtag "^1.0.0"
|
||||
is-typed-array "^1.1.6"
|
||||
|
||||
ws@^7.2.0:
|
||||
version "7.5.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74"
|
||||
integrity sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==
|
||||
108
content/_code-samples/issue-a-token/py/issue-a-token.py
Normal file
108
content/_code-samples/issue-a-token/py/issue-a-token.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# Stand-alone code sample for the "issue a token" tutorial:
|
||||
# https://xrpl.org/issue-a-fungible-token.html
|
||||
# License: https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE
|
||||
|
||||
# Connect ----------------------------------------------------------------------
|
||||
import xrpl
|
||||
testnet_url = "https://s.altnet.rippletest.net:51234"
|
||||
client = xrpl.clients.JsonRpcClient(testnet_url)
|
||||
|
||||
|
||||
# Get credentials from the Testnet Faucet --------------------------------------
|
||||
# For production, instead create a Wallet instance
|
||||
faucet_url = "https://faucet.altnet.rippletest.net/accounts"
|
||||
print("Getting 2 new accounts from the Testnet faucet...")
|
||||
from xrpl.wallet import generate_faucet_wallet
|
||||
cold_wallet = generate_faucet_wallet(client, debug=True)
|
||||
hot_wallet = generate_faucet_wallet(client, debug=True)
|
||||
|
||||
|
||||
# Configure issuer (cold address) settings -------------------------------------
|
||||
cold_settings_tx = xrpl.models.transactions.AccountSet(
|
||||
account=cold_wallet.classic_address,
|
||||
transfer_rate=0,
|
||||
tick_size=5,
|
||||
domain=bytes.hex("example.com".encode("ASCII")),
|
||||
set_flag=xrpl.models.transactions.AccountSetFlag.ASF_DEFAULT_RIPPLE,
|
||||
)
|
||||
cst_prepared = xrpl.transaction.safe_sign_and_autofill_transaction(
|
||||
transaction=cold_settings_tx,
|
||||
wallet=cold_wallet,
|
||||
client=client,
|
||||
)
|
||||
print("Sending cold address AccountSet transaction...")
|
||||
response = xrpl.transaction.send_reliable_submission(cst_prepared, client)
|
||||
print(response)
|
||||
|
||||
|
||||
# Configure hot address settings -----------------------------------------------
|
||||
hot_settings_tx = xrpl.models.transactions.AccountSet(
|
||||
account=hot_wallet.classic_address,
|
||||
set_flag=xrpl.models.transactions.AccountSetFlag.ASF_REQUIRE_AUTH,
|
||||
)
|
||||
hst_prepared = xrpl.transaction.safe_sign_and_autofill_transaction(
|
||||
transaction=hot_settings_tx,
|
||||
wallet=hot_wallet,
|
||||
client=client,
|
||||
)
|
||||
print("Sending hot address AccountSet transaction...")
|
||||
response = xrpl.transaction.send_reliable_submission(hst_prepared, client)
|
||||
print(response)
|
||||
|
||||
|
||||
# Create trust line from hot to cold address -----------------------------------
|
||||
currency_code = "FOO"
|
||||
trust_set_tx = xrpl.models.transactions.TrustSet(
|
||||
account=hot_wallet.classic_address,
|
||||
limit_amount=xrpl.models.amounts.issued_currency_amount.IssuedCurrencyAmount(
|
||||
currency=currency_code,
|
||||
issuer=cold_wallet.classic_address,
|
||||
value="10000000000", # Large limit, arbitrarily chosen
|
||||
)
|
||||
)
|
||||
ts_prepared = xrpl.transaction.safe_sign_and_autofill_transaction(
|
||||
transaction=trust_set_tx,
|
||||
wallet=hot_wallet,
|
||||
client=client,
|
||||
)
|
||||
print("Creating trust line from hot address to issuer...")
|
||||
response = xrpl.transaction.send_reliable_submission(ts_prepared, client)
|
||||
print(response)
|
||||
|
||||
|
||||
# Send token -------------------------------------------------------------------
|
||||
issue_quantity = "3840"
|
||||
send_token_tx = xrpl.models.transactions.Payment(
|
||||
account=cold_wallet.classic_address,
|
||||
destination=hot_wallet.classic_address,
|
||||
amount=xrpl.models.amounts.issued_currency_amount.IssuedCurrencyAmount(
|
||||
currency=currency_code,
|
||||
issuer=cold_wallet.classic_address,
|
||||
value=issue_quantity
|
||||
)
|
||||
)
|
||||
pay_prepared = xrpl.transaction.safe_sign_and_autofill_transaction(
|
||||
transaction=send_token_tx,
|
||||
wallet=cold_wallet,
|
||||
client=client,
|
||||
)
|
||||
print(f"Sending {issue_quantity} {currency_code} to {hot_wallet.classic_address}...")
|
||||
response = xrpl.transaction.send_reliable_submission(pay_prepared, client)
|
||||
print(response)
|
||||
|
||||
|
||||
# Check balances ---------------------------------------------------------------
|
||||
print("Getting hot address balances...")
|
||||
response = client.request(xrpl.models.requests.AccountLines(
|
||||
account=hot_wallet.classic_address,
|
||||
ledger_index="validated",
|
||||
))
|
||||
print(response)
|
||||
|
||||
print("Getting cold address balances...")
|
||||
response = client.request(xrpl.models.requests.GatewayBalances(
|
||||
account=cold_wallet.classic_address,
|
||||
ledger_index="validated",
|
||||
hotwallet=[hot_wallet.classic_address]
|
||||
))
|
||||
print(response)
|
||||
5
content/_code-samples/require-destination-tags/README.md
Normal file
5
content/_code-samples/require-destination-tags/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Require Destination Tags Sample Code
|
||||
|
||||
This code demonstrates how to configure your XRP Ledger account to require destination tags. For a detailed explanation, see <https://xrpl.org/require-destination-tags.html>.
|
||||
|
||||
The code is designed to run in-browser by loading `demo.html` and watching the console output or in Node.js. For Node.js, you must first install the dependencies using your preferred package manager (such as `yarn` or `npm`).
|
||||
@@ -2,9 +2,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
||||
<script type="application/javascript" src="../rippleapi_quickstart/semi-reliable-submit.js"></script>
|
||||
<script src="https://unpkg.com/ripple-lib@1.10.0/build/ripple-latest-min.js"></script>
|
||||
<script type="application/javascript" src="../submit-and-verify/submit-and-verify.js"></script>
|
||||
<script type="application/javascript" src="require-destination-tags.js"></script>
|
||||
</head>
|
||||
<body>Open your browser's console (F12) to see the logs.</body>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"ripple-lib": "^1.10.0"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,22 @@
|
||||
// Stand-alone code sample for the "Require Destination Tags" tutorial:
|
||||
// https://xrpl.org/require-destination-tags.html
|
||||
|
||||
// Dependencies for Node.js.
|
||||
// In browsers, use <script> tags as in the example demo.html.
|
||||
if (typeof module !== "undefined") {
|
||||
// gotta use var here because const/let are block-scoped to the if statement.
|
||||
var ripple = require('ripple-lib')
|
||||
}
|
||||
|
||||
// Connect -------------------------------------------------------------------
|
||||
api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||
api.connect()
|
||||
api.on('connected', async () => {
|
||||
async function main() {
|
||||
console.log("Connecting to Testnet...")
|
||||
const api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||
await api.connect()
|
||||
|
||||
// Get credentials from the Testnet Faucet -----------------------------------
|
||||
// This doesn't technically need to happen after you call api.connect() but
|
||||
// it's convenient to do here because we can use await on the faucet call and
|
||||
// to wait for the new account to be funded.
|
||||
const faucet_url = "https://faucet.altnet.rippletest.net/accounts"
|
||||
const response = await fetch(faucet_url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: '{}'
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw `Faucet returned an error: ${data.error}`
|
||||
}
|
||||
const data = await response.json()
|
||||
console.log("Requesting addresses from the Testnet faucet...")
|
||||
const data = await api.generateFaucetWallet()
|
||||
const address = data.account.address
|
||||
const secret = data.account.secret
|
||||
|
||||
@@ -62,7 +60,7 @@ api.on('connected', async () => {
|
||||
min_ledger: ${min_ledger}`)
|
||||
let tx_status
|
||||
try {
|
||||
tx_status = await lookup_tx_final(tx_id, max_ledger, min_ledger)
|
||||
tx_status = await lookup_tx_final(api, tx_id, max_ledger, min_ledger)
|
||||
} catch(err) {
|
||||
tx_status = err
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
function lookup_tx_final(tx_id, max_ledger, min_ledger) {
|
||||
if (typeof min_ledger == "undefined") {
|
||||
min_ledger = -1
|
||||
}
|
||||
if (typeof max_ledger == "undefined") {
|
||||
max_ledger = -1
|
||||
}
|
||||
if (min_ledger > max_ledger) {
|
||||
// Assume the args were just passed backwards & swap them
|
||||
[min_ledger, max_ledger] = [max_ledger, min_ledger]
|
||||
}
|
||||
|
||||
// Helper to determine if we (should) know the transaction's final result yet.
|
||||
// If the server has validated all ledgers the tx could possibly appear in,
|
||||
// then we should know its final result.
|
||||
async function server_has_ledger_range(min_ledger, max_ledger) {
|
||||
const si = await api.request("server_info")
|
||||
if (si.info.complete_ledgers == "empty") {
|
||||
console.warn("Connected server is not synced.")
|
||||
return false
|
||||
}
|
||||
// In case of a discontiguous set, use only the last set, since we need
|
||||
// continuous history from submission to expiration to know that a
|
||||
// transaction failed to achieve consensus.
|
||||
const ledger_ranges = si.info.complete_ledgers.split(',')
|
||||
// Note: last_range can be in the form 'x-y' or just 'y'
|
||||
const last_range = ledger_ranges[ledger_ranges.length -1].split('-')
|
||||
const lr_min = parseInt(last_range[0])
|
||||
const lr_max = parseInt(last_range[last_range.length - 1])
|
||||
const max_validated = Math.min(lr_max, lr_max)
|
||||
if (lr_min <= min_ledger && max_validated >= max_ledger) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
api.on('ledger', async (ledger) => {
|
||||
try {
|
||||
tx_result = await api.request("tx", {
|
||||
"transaction": tx_id,
|
||||
"min_ledger": min_ledger,
|
||||
"max_ledger": max_ledger
|
||||
})
|
||||
|
||||
if (tx_result.validated) {
|
||||
resolve(tx_result.meta.TransactionResult)
|
||||
} else if (max_ledger > ledger.ledgerVersion) {
|
||||
// Transaction found, not validated, but we should have a final result
|
||||
// by now.
|
||||
// Work around https://github.com/ripple/rippled/issues/3727
|
||||
if (server_has_ledger_range(min_ledger, max_ledger)) {
|
||||
// Transaction should have been validated by now.
|
||||
reject(`Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this ledger is correct.`)
|
||||
} else {
|
||||
reject("Can't get final result. Check a full history server.")
|
||||
}
|
||||
} else {
|
||||
// Transaction may still be validated later. Keep waiting.
|
||||
}
|
||||
} catch(e) {
|
||||
if (e.data.error == "txnNotFound") {
|
||||
if (e.data.searched_all) {
|
||||
reject(`Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`)
|
||||
} else {
|
||||
if (max_ledger > ledger.ledgerVersion) {
|
||||
// Transaction may yet be confirmed. This would not be a bad time
|
||||
// to resubmit the transaction just in case.
|
||||
} else {
|
||||
// Work around https://github.com/ripple/rippled/issues/3750
|
||||
if (server_has_ledger_range(min_ledger, max_ledger)) {
|
||||
reject(`Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`)
|
||||
} else {
|
||||
reject("Can't get final result. Check a full history server.")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unknown error; pass it back up
|
||||
reject(`Unknown Error: ${e}`)
|
||||
}
|
||||
}
|
||||
}) // end ledger event handler
|
||||
}) // end promise def
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
'use strict';
|
||||
/* import RippleAPI and support libraries */
|
||||
const RippleAPI = require('ripple-lib').RippleAPI;
|
||||
|
||||
/* Credentials of the account placing the order */
|
||||
const myAddr = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn';
|
||||
const mySecret = 's████████████████████████████';
|
||||
|
||||
/* Define the order to place here */
|
||||
const myOrder = {
|
||||
'direction': 'buy',
|
||||
'quantity': {
|
||||
'currency': 'FOO',
|
||||
'counterparty': 'rUpy3eEg8rqjqfUoLeBnZkscbKbFsKXC3v',
|
||||
'value': '100'
|
||||
},
|
||||
'totalPrice': {
|
||||
'currency': 'XRP',
|
||||
'value': '1000'
|
||||
}
|
||||
};
|
||||
|
||||
/* Milliseconds to wait between checks for a new ledger. */
|
||||
const INTERVAL = 1000;
|
||||
/* Instantiate RippleAPI. Uses s2 (full history server) */
|
||||
const api = new RippleAPI({server: 'wss://s2.ripple.com'});
|
||||
/* Number of ledgers to check for valid transaction before failing */
|
||||
const ledgerOffset = 5;
|
||||
const myInstructions = {maxLedgerVersionOffset: ledgerOffset};
|
||||
|
||||
|
||||
/* Verify a transaction is in a validated XRP Ledger version */
|
||||
function verifyTransaction(hash, options) {
|
||||
console.log('Verifying Transaction');
|
||||
return api.getTransaction(hash, options).then(data => {
|
||||
console.log('Final Result: ', data.outcome.result);
|
||||
console.log('Validated in Ledger: ', data.outcome.ledgerVersion);
|
||||
console.log('Sequence: ', data.sequence);
|
||||
return data.outcome.result === 'tesSUCCESS';
|
||||
}).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(() => verifyTransaction(hash, options)
|
||||
.then(resolve, reject), INTERVAL);
|
||||
});
|
||||
}
|
||||
return error;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* Function to prepare, sign, and submit a transaction to the XRP Ledger. */
|
||||
function submitTransaction(lastClosedLedgerVersion, prepared, secret) {
|
||||
const signedData = api.sign(prepared.txJSON, secret);
|
||||
return api.submit(signedData.signedTransaction).then(data => {
|
||||
console.log('Tentative Result: ', data.resultCode);
|
||||
console.log('Tentative Message: ', data.resultMessage);
|
||||
/* The tentative result should be ignored. Transactions that succeed here can ultimately fail,
|
||||
and transactions that fail here can ultimately succeed. */
|
||||
|
||||
/* Begin validation workflow */
|
||||
const options = {
|
||||
minLedgerVersion: lastClosedLedgerVersion,
|
||||
maxLedgerVersion: prepared.instructions.maxLedgerVersion
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => verifyTransaction(signedData.id, options)
|
||||
.then(resolve, reject), INTERVAL);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
api.connect().then(() => {
|
||||
console.log('Connected');
|
||||
return api.prepareOrder(myAddr, myOrder, myInstructions);
|
||||
}).then(prepared => {
|
||||
console.log('Order Prepared');
|
||||
return api.getLedger().then(ledger => {
|
||||
console.log('Current Ledger', ledger.ledgerVersion);
|
||||
return submitTransaction(ledger.ledgerVersion, prepared, mySecret);
|
||||
});
|
||||
}).then(() => {
|
||||
api.disconnect().then(() => {
|
||||
console.log('api disconnected');
|
||||
process.exit();
|
||||
});
|
||||
}).catch(console.error);
|
||||
@@ -3,9 +3,8 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Code Sample - Send XRP</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
||||
<script type="application/javascript" src="../rippleapi_quickstart/semi-reliable-submit.js"></script>
|
||||
<script src="https://unpkg.com/ripple-lib@1.10.0/build/ripple-latest-min.js"></script>
|
||||
<script type="application/javascript" src="../submit-and-verify/submit-and-verify.js"></script>
|
||||
<script type="application/javascript" src="send-xrp.js"></script>
|
||||
</head>
|
||||
<body>Open your browser's console (F12) to see the logs.</body>
|
||||
|
||||
@@ -9,21 +9,7 @@ api.connect()
|
||||
api.on('connected', async () => {
|
||||
|
||||
// Get credentials from the Testnet Faucet -----------------------------------
|
||||
// This doesn't technically need to happen after you call api.connect() but
|
||||
// it's convenient to do here because we can use await on the faucet call and
|
||||
// to wait for the new account to be funded.
|
||||
const faucet_url = "https://faucet.altnet.rippletest.net/accounts"
|
||||
const response = await fetch(faucet_url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: '{}'
|
||||
})
|
||||
if (!response.ok) {
|
||||
throw `Faucet returned an error: ${data.error}`
|
||||
}
|
||||
const data = await response.json()
|
||||
const data = await api.generateFaucetWallet()
|
||||
address = data.account.address
|
||||
secret = data.account.secret
|
||||
|
||||
|
||||
32
content/_code-samples/submit-and-verify/README.md
Normal file
32
content/_code-samples/submit-and-verify/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Submit and Verify
|
||||
|
||||
Example JavaScript code using ripple-lib to submit a signed transaction blob and wait until it has a final result.
|
||||
|
||||
Example usage:
|
||||
|
||||
```js
|
||||
// example testnet creds. Don't use for real
|
||||
const address = "raXDGCShEGqYz2d94qkv1UmAh2uJd3UTea"
|
||||
const secret = "ssNBEKCkEY3W6YFfrjcSoNit91Vvj"
|
||||
api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||
api.connect()
|
||||
api.on('connected', async () => {
|
||||
const prepared = await api.prepareTransaction({
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": address
|
||||
})
|
||||
const signed = api.sign(prepared.txJSON, secret)
|
||||
|
||||
const result = await submit_and_verify(api, signed.signedTransaction)
|
||||
|
||||
if (result == "tesSUCCESS") {
|
||||
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${signed.id}`)
|
||||
} else if (result == "unknown") {
|
||||
console.log(`Transaction status unknown. `)
|
||||
} else if (result == "tefMAX_LEDGER") {
|
||||
console.log(`Transaction failed to achieve a consensus.`)
|
||||
} else {
|
||||
console.log(`Transaction failed with code ${result}: https://testnet.xrpl.org/transactions/${signed.id}`)
|
||||
}
|
||||
}
|
||||
```
|
||||
175
content/_code-samples/submit-and-verify/submit-and-verify.js
Normal file
175
content/_code-samples/submit-and-verify/submit-and-verify.js
Normal file
@@ -0,0 +1,175 @@
|
||||
// Submit-and-verify XRPL transaction using ripple-lib (v1.x)
|
||||
// Demonstrates how to submit a transaction and wait for validation.
|
||||
// This is not true "robust" transaction submission because it does not protect
|
||||
// against power outages or other sudden interruptions.
|
||||
|
||||
// Look up a transaction's result.
|
||||
// Arguments:
|
||||
// @param api object RippleAPI instance connected to the network where you
|
||||
// submitted the transaction.
|
||||
// @param tx_id string The identifying hash of the transaction.
|
||||
// @param max_ledger int optional The highest ledger index where the
|
||||
// transaction can be validated.
|
||||
// @param min_ledger int optional The lowest ledger index where the
|
||||
// transaction can be validated.
|
||||
// Returns: Promise<object> -> result of the tx command with the transaction's
|
||||
// validated transaction results.
|
||||
// On failure, the reason is an object with two fields:
|
||||
// - failure_final: if true, this transaction did not achieve consensus and
|
||||
// it can never be validated in the future (assuming the
|
||||
// min_ledger and max_ledger values provided were accurate).
|
||||
// - msg: A human-readable message explaining what happened.
|
||||
function lookup_tx_final(api, tx_id, max_ledger, min_ledger) {
|
||||
if (typeof min_ledger == "undefined") {
|
||||
min_ledger = -1
|
||||
}
|
||||
if (typeof max_ledger == "undefined") {
|
||||
max_ledger = -1
|
||||
}
|
||||
if (min_ledger > max_ledger) {
|
||||
// Assume the args were just passed backwards & swap them
|
||||
[min_ledger, max_ledger] = [max_ledger, min_ledger]
|
||||
}
|
||||
|
||||
// Helper to determine if we (should) know the transaction's final result yet.
|
||||
// If the server has validated all ledgers the tx could possibly appear in,
|
||||
// then we should know its final result.
|
||||
async function server_has_ledger_range(min_ledger, max_ledger) {
|
||||
const si = await api.request("server_info")
|
||||
// console.log(`Server has ledger range: ${si.info.complete_ledgers}`)
|
||||
if (si.info.complete_ledgers == "empty") {
|
||||
console.warn("Connected server is not synced.")
|
||||
return false
|
||||
}
|
||||
// In case of a discontiguous set, use only the last set, since we need
|
||||
// continuous history from submission to expiration to know that a
|
||||
// transaction failed to achieve consensus.
|
||||
const ledger_ranges = si.info.complete_ledgers.split(',')
|
||||
// Note: last_range can be in the form 'x-y' or just 'y'
|
||||
const last_range = ledger_ranges[ledger_ranges.length -1].split('-')
|
||||
const lr_min = parseInt(last_range[0])
|
||||
const lr_max = parseInt(last_range[last_range.length - 1])
|
||||
if (lr_min <= min_ledger && lr_max >= max_ledger) {
|
||||
// Server has ledger range needed.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ledger_listener = async (ledger) => {
|
||||
try {
|
||||
tx_result = await api.request("tx", {
|
||||
"transaction": tx_id,
|
||||
"min_ledger": min_ledger,
|
||||
"max_ledger": max_ledger
|
||||
})
|
||||
|
||||
if (tx_result.validated) {
|
||||
resolve(tx_result.meta.TransactionResult)
|
||||
} else if (ledger.ledgerVersion >= max_ledger) {
|
||||
api.off("ledger", ledger_listener)
|
||||
// Transaction found, not validated, but we should have a final result
|
||||
// by now.
|
||||
// Work around https://github.com/ripple/rippled/issues/3727
|
||||
if (await server_has_ledger_range(min_ledger, max_ledger)) {
|
||||
// Transaction should have been validated by now.
|
||||
reject({
|
||||
failure_final: true,
|
||||
msg: `Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`
|
||||
})
|
||||
} else {
|
||||
reject({
|
||||
failure_final: false,
|
||||
msg: "Can't get final result (1). Check a full history server."
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Transaction may still be validated later. Keep waiting.
|
||||
}
|
||||
} catch(e) {
|
||||
console.warn(e)
|
||||
if (e.data.error == "txnNotFound") {
|
||||
if (e.data.searched_all) {
|
||||
api.off("ledger", ledger_listener)
|
||||
reject({
|
||||
failure_final: true,
|
||||
msg: `Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`
|
||||
})
|
||||
} else {
|
||||
if (max_ledger > ledger.ledgerVersion) {
|
||||
api.off("ledger", ledger_listener)
|
||||
// Transaction may yet be confirmed. This would not be a bad time
|
||||
// to resubmit the transaction just in case.
|
||||
} else {
|
||||
// Work around https://github.com/ripple/rippled/issues/3750
|
||||
if (await server_has_ledger_range(min_ledger, max_ledger)) {
|
||||
reject({
|
||||
failure_final: true,
|
||||
msg: `Transaction not found in ledgers ${min_ledger}-${max_ledger}. This result is final if this range is correct.`
|
||||
})
|
||||
} else {
|
||||
reject({
|
||||
failure_final: false,
|
||||
msg: "Can't get final result. Check a full history server."
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Unknown error; pass it back up
|
||||
reject({
|
||||
failure_final: false,
|
||||
msg: `Unknown Error: ${e}`
|
||||
})
|
||||
}
|
||||
}
|
||||
} // end ledger event handler
|
||||
api.on('ledger', ledger_listener)
|
||||
}) // end promise def
|
||||
}
|
||||
|
||||
|
||||
// Submit a transaction blob and get its final result as a string.
|
||||
// This can be one of these possibilities:
|
||||
// tesSUCCESS. The transaction executed successfully.
|
||||
// tec*. The transaction was validated with a failure code. It destroyed the XRP
|
||||
// transaction cost and may have done some cleanup such as removing
|
||||
// expired objects from the ledger, but nothing else.
|
||||
// See https://xrpl.org/tec-codes.html for the full list.
|
||||
// tefMAX_LEDGER. The transaction expired without ever being included
|
||||
// in a validated ledger.
|
||||
// unknown. Either the server you are querying does not have the
|
||||
// necessary ledger history to find the transaction's final result, or
|
||||
// something else went wrong when trying to look up the results. The
|
||||
// warning written to the console can tell you more about what happened.
|
||||
async function submit_and_verify(api, tx_blob) {
|
||||
const prelim_result = await api.request("submit", {"tx_blob": tx_blob})
|
||||
console.log("Preliminary result code:", prelim_result.engine_result)
|
||||
const min_ledger = prelim_result.validated_ledger_index
|
||||
if (prelim_result.tx_json.LastLedgerSequence === undefined) {
|
||||
console.warn("Transaction has no LastLedgerSequence field. "+
|
||||
"It may be impossible to determine final failure.")
|
||||
}
|
||||
const max_ledger = prelim_result.tx_json.LastLedgerSequence
|
||||
const tx_id = prelim_result.tx_json.hash
|
||||
|
||||
let final_result
|
||||
try {
|
||||
final_result = await lookup_tx_final(api, tx_id, max_ledger, min_ledger)
|
||||
} catch(reason) {
|
||||
if (reason.failure_final) final_result = "tefMAX_LEDGER"
|
||||
else final_result = "unknown"
|
||||
console.warn(reason)
|
||||
}
|
||||
|
||||
return final_result;
|
||||
}
|
||||
|
||||
// Exports for node.js; no-op for browsers
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = {
|
||||
submit_and_verify: submit_and_verify,
|
||||
lookup_tx_final: lookup_tx_final
|
||||
}
|
||||
}
|
||||
@@ -244,7 +244,7 @@ Promiseは、自身の非同期動作を完了すると、渡されたコール
|
||||
XRP Ledger(または任意の分散されたシステム)を使用する上で最大の課題の1つとなるのが、最終的かつ不変のトランザクション結果を把握することです。[ベストプラクティスに従っている](reliable-transaction-submission.html)場合も、トランザクションが最終的に受け入れられるか拒否されるまで、[コンセンサスプロセス](consensus.html)を待機しなければならないことに変わりはありません。以下のサンプルコードは、トランザクションの最終的な結果を待機する方法を示しています。
|
||||
|
||||
```
|
||||
{% include '_code-samples/rippleapi_quickstart/submit-and-verify.js' %}
|
||||
{% include '_code-samples/submit-and-verify/submit-and-verify.js' %}
|
||||
```
|
||||
|
||||
このコードは注文トランザクションを作成して送信するものですが、他のタイプのトランザクションにも同様の原則があてはまります。トランザクションを送信した後、setTimeoutを使用して所定の時間が経過するまで待機し、新しいPromiseでレジャーをもう一度照会して、トランザクションが検証済みとなっているかどうかを確認します。検証済みとなっていない場合は、検証済みレジャーの中にトランザクションが見つかるか、返されたレジャーがLastLedgerSequenceパラメーターの値よりも大きくなるまで、このプロセスを繰り返します。
|
||||
|
||||
@@ -242,17 +242,13 @@ The `catch` method ends this Promise chain. The callback provided here runs if a
|
||||
|
||||
# Waiting for Validation
|
||||
|
||||
One of the biggest challenges in using the XRP Ledger (or any decentralized system) is knowing the final, immutable transaction results. Even if you [follow the best practices](reliable-transaction-submission.html) you still have to wait for the [consensus process](consensus.html) to finally accept or reject your transaction. The following example code demonstrates how to wait for the final outcome of a transaction:
|
||||
Most transactions are validated and have a final result in one or two ledger versions, about 2-7 seconds after submission. However, when things don't go quite as planned, it can be tricky to know what a transaction's final, immutable results are. Even if you [follow the best practices](reliable-transaction-submission.html) you still have to wait for the [consensus process](consensus.html) to finally accept or reject your transaction.
|
||||
|
||||
```
|
||||
{% include '_code-samples/rippleapi_quickstart/submit-and-verify.js' %}
|
||||
```
|
||||
The [submit-and-verify code sample](https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/submit-and-verify/) demonstrates how to submit a transaction and wait for it to have a final result.
|
||||
|
||||
This code creates and submits an order transaction, although the same principles apply to other types of transactions as well. After submitting the transaction, the code uses a new Promise, which queries the ledger again after using `setTimeout` to wait a fixed amount of time, to see if the transaction has been verified. If it hasn't been verified, the process repeats until either the transaction is found in a validated ledger or the returned ledger is higher than the `LastLedgerSequence` parameter.
|
||||
In rare cases (particularly with a large delay, a brief network outage, or a loss of power), the `rippled` server may be missing a ledger version between when you submitted the transaction and when you determined that the network validated the last ledger version that the transaction . In this case, you cannot be definitively sure whether the transaction has failed, or has been included in one of the missing ledger versions.
|
||||
|
||||
In rare cases (particularly with a large delay or a loss of power), the `rippled` server may be missing a ledger version between when you submitted the transaction and when you determined that the network has passed the `maxLedgerVersion`. In this case, you cannot be definitively sure whether the transaction has failed, or has been included in one of the missing ledger versions. RippleAPI returns `MissingLedgerHistoryError` in this case.
|
||||
|
||||
If you are the administrator of the `rippled` server, you can [manually request the missing ledger(s)](ledger_request.html). Otherwise, you can try checking the ledger history using a different server. (Ripple runs a public full-history server at `s2.ripple.com` for this purpose.)
|
||||
If you are the administrator of the `rippled` server, you can [manually request the missing ledger(s)](ledger_request.html). Otherwise, you can try checking the ledger history using a different server. Several [public full-history servers](public-servers.html) are available for this purpose.
|
||||
|
||||
See [Reliable Transaction Submission](reliable-transaction-submission.html) for a more thorough explanation.
|
||||
|
||||
@@ -260,22 +256,20 @@ See [Reliable Transaction Submission](reliable-transaction-submission.html) for
|
||||
|
||||
# RippleAPI in Web Browsers
|
||||
|
||||
RippleAPI can also be used in a web browser. To access it, load [Lodash](https://lodash.com/) and [RippleAPI for JavaScript (ripple-lib)](rippleapi-reference.html) in your site's HTML. For example:
|
||||
RippleAPI can also be used in a web browser. To access it, load [RippleAPI for JavaScript (ripple-lib)](rippleapi-reference.html) in your site's HTML. For example:
|
||||
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_unpkg_
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/lodash@4.17.20/lodash.min.js"></script>
|
||||
<script src="https://unpkg.com/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
||||
<script src="https://unpkg.com/ripple-lib@1.10.0/build/ripple-latest-min.js"></script>
|
||||
```
|
||||
|
||||
_jsDelivr_
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/ripple-lib@1.10.0/build/ripple-latest-min.js"></script>
|
||||
```
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
@@ -16,7 +16,6 @@ The following example gets the latest [ledger version](ledgers.html) and a list
|
||||
**Tip:** If you can, open your browser's Developer Tools by pressing **F12**. The "Console" tab provides a native JavaScript console and can give insight into what code is running on any webpage. <!-- SPELLING_IGNORE: f12 -->
|
||||
|
||||
<!-- ripple-lib & prerequisites -->
|
||||
{{currentpage.lodash_tag}}
|
||||
{{currentpage.ripple_lib_tag}}
|
||||
|
||||
<!-- JS_EDITOR_START step2 -->
|
||||
@@ -101,14 +100,12 @@ Try editing the code above to do something different:
|
||||
|
||||
## Setup Steps
|
||||
|
||||
This page has the necessary prerequisites already loaded, but you can access the XRP Ledger from **any webpage** if you load [Lodash](https://lodash.com/) and [RippleAPI for JavaScript (ripple-lib)](rippleapi-reference.html) in that page's HTML. For example:
|
||||
This page has the necessary prerequisites already loaded, but you can access the XRP Ledger from **any webpage** if you load [RippleAPI for JavaScript (ripple-lib)](rippleapi-reference.html) in that page's HTML. For example:
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
|
||||
<script src="https://unpkg.com/ripple-lib@1.9.1/build/ripple-latest-min.js"></script>
|
||||
<script src="https://unpkg.com/ripple-lib@1.10.0/build/ripple-latest-min.js"></script>
|
||||
```
|
||||
|
||||
<!-- SPELLING_IGNORE: lodash -->
|
||||
|
||||
## Further Reading
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ const socket = new WebSocket('ws://localhost:6006')
|
||||
例:
|
||||
|
||||
{{ start_step("Connect") }}
|
||||
<button id="connect-button" class="btn btn-primary">Connect</button>
|
||||
<button id="connect-socket-button" class="btn btn-primary">Connect</button>
|
||||
<strong>Connection status:</strong>
|
||||
<span id="connection-status">Not connected</span>
|
||||
<h5>Console:</h5>
|
||||
@@ -85,7 +85,7 @@ const socket = new WebSocket('ws://localhost:6006')
|
||||
|
||||
<script type="application/javascript">
|
||||
let socket;
|
||||
$("#connect-button").click((event) => {
|
||||
$("#connect-socket-button").click((event) => {
|
||||
socket = new WebSocket('wss://s.altnet.rippletest.net:51233')
|
||||
socket.addEventListener('open', (event) => {
|
||||
// This callback runs when the connection is open
|
||||
|
||||
@@ -74,7 +74,7 @@ const socket = new WebSocket('ws://localhost:6006')
|
||||
Example:
|
||||
|
||||
{{ start_step("Connect") }}
|
||||
<button id="connect-button" class="btn btn-primary">Connect</button>
|
||||
<button id="connect-socket-button" class="btn btn-primary">Connect</button>
|
||||
<strong>Connection status:</strong>
|
||||
<span id="connection-status">Not connected</span>
|
||||
<h5>Console:</h5>
|
||||
@@ -83,7 +83,7 @@ Example:
|
||||
|
||||
<script type="application/javascript">
|
||||
let socket;
|
||||
$("#connect-button").click((event) => {
|
||||
$("#connect-socket-button").click((event) => {
|
||||
socket = new WebSocket('wss://s.altnet.rippletest.net:51233')
|
||||
socket.addEventListener('open', (event) => {
|
||||
// This callback runs when the connection is open
|
||||
|
||||
@@ -42,7 +42,7 @@ When you're [building actual production-ready software](production-readiness.htm
|
||||
|
||||
You must be connected to the network to submit transactions to it.
|
||||
|
||||
The following code uses a [ripple-lib for JavaScript](rippleapi-reference.html) instance to connect to a public XRP Testnet server:
|
||||
The following code uses a [ripple-lib for JavaScript](rippleapi-reference.html) instance to connect to a public XRP Ledger Testnet server:
|
||||
|
||||
```js
|
||||
ripple = require('ripple-lib') // Node.js only. Use a <script> tag in browsers.
|
||||
|
||||
530
content/tutorials/use-tokens/issue-a-fungible-token.md
Normal file
530
content/tutorials/use-tokens/issue-a-fungible-token.md
Normal file
@@ -0,0 +1,530 @@
|
||||
---
|
||||
html: issue-a-fungible-token.html
|
||||
parent: use-tokens.html
|
||||
blurb: Create your own token and issue it on the XRP Ledger Testnet.
|
||||
embed_ripple_lib: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
- include_code
|
||||
labels:
|
||||
- Tokens
|
||||
---
|
||||
# Issue a Fungible Token
|
||||
|
||||
Anyone can issue various types of tokens in the XRP Ledger, ranging from informal "IOUs" to fiat-backed stablecoins, purely digital fungible and semi-fungible tokens, and more. This tutorial demonstrates the technical steps of creating a token in the ledger. For more information on how XRP Ledger tokens work, see [Issued Currencies](issued-currencies.html); for more on the business decisions involved in issuing a stablecoin, see [Become an XRP Ledger Gateway](become-an-xrp-ledger-gateway.html).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You need two funded XRP Ledger accounts, each with an address, secret key, and some XRP. For this tutorial, you can generate new test credentials as needed.
|
||||
- Each address needs enough XRP to satisfy the [reserve requirement](reserves.html) including the additional reserve for a trust line.
|
||||
- You need a connection to the XRP Ledger network. As shown in this tutorial, you can use public servers for testing.
|
||||
- You should be familiar with the Getting Started instructions for your preferred client library. This page provides examples for the following:
|
||||
- ripple-lib for JavaScript [(Node.js)](get-started-with-rippleapi-for-javascript.html) or [in-browser](get-started.html)
|
||||
- [xrpl-py for Python](get-started-using-python.html)
|
||||
- [xrpl4j for Java](get-started-using-java.html).
|
||||
- You can also read along and use the interactive steps in your browser without any setup.
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="assets/js/tutorials/issue-a-token.js"></script>
|
||||
|
||||
## Example Code
|
||||
|
||||
Complete sample code for all of the steps of these tutorials is available under the [MIT license](https://github.com/XRPLF/xrpl-dev-portal/blob/master/LICENSE).
|
||||
|
||||
- See [Code Samples: Issue a Fungible Token](https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/issue-a-token/) in the source repository for this website.
|
||||
|
||||
## Steps
|
||||
{% set n = cycler(* range(1,99)) %}
|
||||
|
||||
### {{n.next()}}. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. You also need one or more recipients who are willing to hold the tokens you issue: unlike in some other blockchains, in the XRP Ledger you cannot force someone to hold a token they do not want.
|
||||
|
||||
The best practice is to use ["cold" and "hot" addresses](issuing-and-operational-addresses.html). The cold address is the **issuer** of the token. The hot address is like a regular user's address that you control. It receives tokens from the cold address, which you can then transfer to other users. A hot address is not strictly necessary, since you could send tokens directly to users from the cold address, but it is good practice for security reasons. In production, you should take extra care of the cold address's cryptographic keys (for example, keeping them offline) because it is much harder to replace a cold address than a hot address.
|
||||
|
||||
In this tutorial, the hot address receives the tokens you issue from the cold address. You can get the keys for two addresses using the following interface.
|
||||
|
||||
<!-- Special version of generate-step.md for getting sender AND receiver credentials -->
|
||||
{% if use_network is undefined or use_network == "Testnet" %}
|
||||
{% set use_network = "Testnet" %}
|
||||
{% set faucet_url = "https://faucet.altnet.rippletest.net/accounts" %}
|
||||
{% elif use_network == "Devnet" %}
|
||||
{% set faucet_url = "https://faucet.devnet.rippletest.net/accounts" %}
|
||||
{# No faucet for Mainnet! #}
|
||||
{% endif %}
|
||||
{{ start_step("Generate") }}
|
||||
<button id="generate-2x-creds-button" class="btn btn-primary" data-fauceturl="{{faucet_url}}">Get {{use_network}} credentials</button>
|
||||
<div class="loader collapse"><img class="throbber" src="assets/img/xrp-loader-96.png">Generating Keys...</div>
|
||||
<div class="output-area"></div>
|
||||
{{ end_step() }}
|
||||
|
||||
**Caution:** Ripple provides the [Testnet and Devnet](parallel-networks.html) for testing purposes only, and sometimes resets the state of these test networks along with all balances. As a precaution, **do not** use the same addresses on Testnet/Devnet and Mainnet.
|
||||
|
||||
When you're [building actual production-ready software](production-readiness.html), you'll instead use an existing account, and manage your keys using a [secure signing configuration](set-up-secure-signing.html).
|
||||
|
||||
|
||||
### {{n.next()}}. Connect to the Network
|
||||
|
||||
You must be connected to the network to submit transactions to it.
|
||||
|
||||
The following code shows how to connect to a public XRP Ledger Testnet server a supported [client library](client-libraries.html):
|
||||
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
```js
|
||||
ripple = require('ripple-lib') // Node.js only. Use a <script> tag in browsers.
|
||||
|
||||
async function main() {
|
||||
api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||
await api.connect()
|
||||
|
||||
// Code in the following examples continues here...
|
||||
|
||||
api.disconnect() // When done. This lets Node.js stop running.
|
||||
}
|
||||
|
||||
main()
|
||||
```
|
||||
|
||||
_Python_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/py/issue-a-token.py", start_with="# Connect", end_before="# Get credentials", language="py") }}
|
||||
|
||||
_Java_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/java/IssueToken.java", start_with="// Construct a network client", end_before="// Create cold", language="java") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
|
||||
**Note:** The JavaScript code samples in this tutorial use the [`async`/`await` pattern](https://javascript.info/async-await). Since `await` needs to be used from within an `async` function, the remaining code samples are written to continue inside the `main()` function started here. You can also use Promise methods `.then()` and `.catch()` instead of `async`/`await` if you prefer.
|
||||
|
||||
For this tutorial, you can connect directly from your browser by pressing the following button:
|
||||
|
||||
{% include '_snippets/interactive-tutorials/connect-step.md' %}
|
||||
|
||||
|
||||
### {{n.next()}}. Configure Issuer Settings
|
||||
|
||||
First, configure the settings for your cold address (which will become the issuer of your token). Most settings can be reconfigured later, with the following exceptions: <!-- STYLE_OVERRIDE: will -->
|
||||
|
||||
- [Default Ripple][]: **This setting is required** so that users can send your token to each other. It's best to enable it _before_ setting up any trust lines or issuing any tokens.
|
||||
- [Authorized Trust Lines][]: (Optional) This setting (also called "Require Auth") limits your tokens to being held _only_ by accounts you've explicitly approved. You cannot enable this setting if you already have any trust lines or offers for _any_ token.
|
||||
**Note:** To use authorized trust lines, you must perform additional steps that are not shown in this tutorial.
|
||||
|
||||
[Default Ripple]: rippling.html
|
||||
[Authorized Trust Lines]: authorized-trust-lines.html
|
||||
|
||||
Other settings you may want to, optionally, configure for your cold address (issuer):
|
||||
|
||||
| Setting | Recommended Value | Summary |
|
||||
|:-----------------------------|:--------------------|:------------------------|
|
||||
| [Require Destination Tags][] | Enabled or Disabled | Enable if you process withdrawals of your token to outside systems. (For example, your token is a stablecoin.) |
|
||||
| Disallow XRP | Enabled or Disabled | Enable if this address isn't meant to process XRP payments. |
|
||||
| [Transfer Fee][] | 0–1% | Charge a percentage fee when users send your token to each other. |
|
||||
| [Tick Size][] | 5 | Limit the number of decimal places in exchange rates for your token in the [decentralized exchange](decentralized-exchange.html). A tick size of 5-6 reduces churn of almost-equivalent offers and speeds up price discovery compared to the default of 15. |
|
||||
| [Domain][] | (Your domain name) | Set to a domain you own so can [verify ownership of the accounts](xrp-ledger-toml.html#account-verification). This can help reduce confusion or impersonation attempts. |
|
||||
|
||||
[Require Destination Tags]: require-destination-tags.html
|
||||
[Transfer Fee]: transfer-fees.html
|
||||
[Tick Size]: ticksize.html
|
||||
[Domain]: accountset.html#domain
|
||||
|
||||
You can change these settings later as well.
|
||||
|
||||
**Note:** Many issuing settings apply equally to all tokens issued by an address, regardless of the currency code. If you want to issue multiple types of tokens in the XRP Ledger with different settings, you should use a different address to issue each different token.
|
||||
|
||||
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended cold address settings:
|
||||
|
||||
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/js/issue-a-token.js", start_with="// Configure issuer", end_before="// Configure hot", language="js") }}
|
||||
|
||||
_Python_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/py/issue-a-token.py", start_with="# Configure issuer", end_before="# Configure hot", language="py") }}
|
||||
|
||||
_Java_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/java/IssueToken.java", start_with="// Configure issuer", end_before="// Configure hot", language="java") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Configure Issuer") }}
|
||||
<form>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="cold-default-ripple" disabled="disabled" checked title="The issuer MUST enable Default Ripple first." />
|
||||
<label for="cold-default-ripple" class="col-form-label" title="The issuer MUST enable Default Ripple first.">Default Ripple</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="cold-require-dest" />
|
||||
<label for="cold-require-dest" class="col-form-label">Require Destination Tags</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="cold-disallow-xrp" checked />
|
||||
<label for="cold-disallow-xrp" class="col-form-label">Disallow XRP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="cold-transfer-fee" class="col-form-label col-sm-3">Transfer Fee</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" id="cold-transfer-fee" value="0" step="0.0000001" min="0" max="100" />
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="cold-tick-size" class="col-form-label col-sm-3">Tick Size</label>
|
||||
<div class="input-group col">
|
||||
<input type="number" class="form-control" id="cold-tick-size" value="5" step="1" min="0" max="15" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="cold-domain-text" class="col-form-label col-sm-3">Domain</label>
|
||||
<div class="input-group col">
|
||||
<div>
|
||||
<input type="text" class="form-control" value="example.com" pattern="([a-z0-9][a-z0-9-]{0,62}[.])+[a-z]{2,6}" id="cold-domain-text" title="lower-case domain name of the account owner" />
|
||||
<small class="form-text">(text)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group col">
|
||||
<div class="col">
|
||||
<label class="form-control-plaintext" id="cold-domain-hex" for="cold-domain-text">6578616D706C652E636F6D</label>
|
||||
<small class="form-text">(hexadecimal)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="config-issuer-button" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait (Issuer Setup)">Configure issuer</button>
|
||||
<div class="loader collapse"><img class="throbber" src="assets/img/xrp-loader-96.png">Sending transaction...</div>
|
||||
<div class="output-area"></div>
|
||||
{{ end_step() }}
|
||||
|
||||
### {{n.next()}}. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. You should wait for your earlier transactions to be fully validated before proceeding to the later steps, to avoid unexpected failures from things executing out of order. For more information, see [Reliable Transaction Submission](reliable-transaction-submission.html).
|
||||
|
||||
The code samples in this tutorial use helper functions to wait for validation when submitting a transaction:
|
||||
|
||||
- **JavaScript:** The `submit_and_verify()` function, as defined in the [submit-and-verify code sample](https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/submit-and-verify).
|
||||
- **Python:** The `send_reliable_submission()` [method of the xrpl-py library](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.send_reliable_submission).
|
||||
- **Java:** The `submitAndWaitForValidation()` method in the [sample Java class](https://github.com/XRPLF/xrpl-dev-portal/blob/master/content/_code-samples/issue-a-token/java/IssueToken.java).
|
||||
|
||||
**Tip:** Technically, you can configure the hot address in parallel with configuring the issuer address. For simplicity, this tutorial waits for each transaction one at a time.
|
||||
|
||||
{{ start_step("Wait (Issuer Setup)") }}
|
||||
{% include '_snippets/interactive-tutorials/wait-step.md' %}
|
||||
{{ end_step() }}
|
||||
|
||||
|
||||
### {{n.next()}}. Configure Hot Address Settings
|
||||
|
||||
The hot address does not strictly require any settings changes from the default, but the following are recommended as best practices:
|
||||
|
||||
| Setting | Recommended Value | Summary |
|
||||
|:-----------------------------|:--------------------|:------------------------|
|
||||
| [Default Ripple][] | Disabled | Leave this setting **disabled.** (This is the default.) |
|
||||
| [Authorized Trust Lines][] | Enabled | Enable this setting on the hot address—and never approve any trust lines to the hot address—to prevent accidentally issuing tokens from the wrong address. (Optional, but recommended.) |
|
||||
| [Require Destination Tags][] | Enabled or Disabled | Enable if you process withdrawals of your token to outside systems. (For example, your token is a stablecoin.) |
|
||||
| Disallow XRP | Enabled or Disabled | Enable if this address isn't meant to process XRP payments. |
|
||||
| [Domain][] | (Your domain name) | Set to a domain you own so can [verify ownership of the accounts](xrp-ledger-toml.html#account-verification). This can help reduce confusion or impersonation attempts. |
|
||||
|
||||
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended hot address settings:
|
||||
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/js/issue-a-token.js", start_with="// Configure hot address", end_before="// Create trust line", language="js") }}
|
||||
|
||||
_Python_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/py/issue-a-token.py", start_with="# Configure hot address", end_before="# Create trust line", language="py") }}
|
||||
|
||||
_Java_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/java/IssueToken.java", start_with="// Configure hot address", end_before="// Create trust line", language="java") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Configure Hot Address") }}
|
||||
<form>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="hot-default-ripple" disabled="disabled" title="Default Ripple must remain disabled on the hot address." />
|
||||
<label for="hot-default-ripple" class="form-check-label" title="Default Ripple must remain disabled on the hot address.">Default Ripple</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="hot-require-auth" disabled="disabled" checked="checked" title="The hot address should enable Authorized Trust Lines as a precaution." />
|
||||
<label for="hot-default-ripple" class="form-check-label" title="The hot address should enable Authorized Trust Lines as a precaution.">Authorized Trust Lines</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="hot-require-dest" />
|
||||
<label for="hot-require-dest" class="form-check-label">Require Destination Tags</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline">
|
||||
<div class="input-group form-check">
|
||||
<input type="checkbox" class="form-check-input mr-3" value="" id="hot-disallow-xrp" checked="checked" />
|
||||
<label for="hot-disallow-xrp" class="form-check-label">Disallow XRP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="hot-domain-text" class="col-form-label col-sm-3">Domain</label>
|
||||
<div class="input-group col">
|
||||
<div>
|
||||
<input type="text" class="form-control" value="example.com" pattern="([a-z0-9][a-z0-9-]{0,62}[.])+[a-z]{2,6}" id="hot-domain-text" title="lower-case domain name of the account owner" />
|
||||
<small class="form-text">(text)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group col">
|
||||
<div class="col">
|
||||
<label class="form-control-plaintext" id="hot-domain-hex" for="hot-domain-text">6578616D706C652E636F6D</label>
|
||||
<small class="form-text">(hexadecimal)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="config-hot-address-button" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait (Hot Address Setup)">Configure hot address</button>
|
||||
<div class="loader collapse"><img class="throbber" src="assets/img/xrp-loader-96.png">Sending transaction...</div>
|
||||
<div class="output-area"></div>
|
||||
{{ end_step() }}
|
||||
|
||||
### {{n.next()}}. Wait for Validation
|
||||
|
||||
As before, wait for the previous transaction to be validated by consensus before continuing.
|
||||
|
||||
{{ start_step("Wait (Hot Address Setup)") }}
|
||||
{% include '_snippets/interactive-tutorials/wait-step.md' %}
|
||||
{{ end_step() }}
|
||||
|
||||
|
||||
### {{n.next()}}. Create Trust Line from Hot to Cold Address
|
||||
|
||||
Before you can receive tokens, you need to create a [trust line](trust-lines-and-issuing.html) to the token issuer. This trust line is specific to the [currency code](currency-formats.html#currency-codes) of the token you want to issue, such as USD or FOO. You can choose any currency code you want; each issuer's tokens are treated as separate in the XRP Ledger protocol. However, users' balances of tokens with the same currency code can [ripple](rippling.html) between different issuers if the users enable rippling settings.
|
||||
|
||||
The hot address needs a trust line like this before it can receive tokens from the issuer. Similarly, each user who wants to hold your token must also create a trust line[¹](#footnotes). Each trust line increases the [reserve requirement](reserves.html) of the hot address, so you must hold enough spare XRP to pay for the increased requirement. Your reserve requirement goes back down if you remove the trust line.
|
||||
|
||||
**Tip:** A trust line has a "limit" on how much the recipient is willing to hold; others cannot send you more tokens than your specified limit. For community credit systems, you may want to configure limits per individual based on how much you trust that person. For other types and uses of tokens, it is normally OK to set the limit to a very large number.
|
||||
|
||||
To create a trust line, send a [TrustSet transaction][] from the **hot address** with the following fields:
|
||||
|
||||
| Field | Value |
|
||||
|:-----------------------|:----------------------------------------------------|
|
||||
| `TransactionType` | `"TrustSet"` |
|
||||
| `Account` | The hot address. (More generally, this is the account that wants to receive the token.) |
|
||||
| `LimitAmount` | An object specifying how much, of which token, from which issuer, you are willing to hold. |
|
||||
| `LimitAmount.currency` | The currency code of the token. |
|
||||
| `LimitAmount.issuer` | The cold address. |
|
||||
| `LimitAmount.value` | The maximum amount of the token you are willing to hold. |
|
||||
|
||||
The following code sample shows how to send a [TrustSet transaction][] from the hot address, trusting the issuing address for a limit of 1 billion FOO:
|
||||
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/js/issue-a-token.js", start_with="// Create trust line", end_before="// Send token", language="js") }}
|
||||
|
||||
_Python_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/py/issue-a-token.py", start_with="# Create trust line", end_before="# Send token", language="py") }}
|
||||
|
||||
_Java_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/java/IssueToken.java", start_with="// Create trust line", end_before="// Send token", language="java") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Make Trust Line") }}
|
||||
<form>
|
||||
<p>Currency code:</p>
|
||||
<div class="container">
|
||||
<div class="input-group row">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text form-check bg-transparent">
|
||||
<input type="radio" id="use-std-code" name="currency-code-type" checked="checked" />
|
||||
</div>
|
||||
</div>
|
||||
<label for="use-std-code" class="input-group-text col-lg-3">Standard:</label>
|
||||
<input type="text" id="currency-code-std" pattern="[A-Za-z0-9?!@#$%*(){}|\x26\x3c\x3e]{3}" value="FOO" class="form-control col-lg-8" title="3 character code (letters, numbers, and some symbols)" />
|
||||
</div>
|
||||
|
||||
<div class="input-group row mt-2">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text form-check bg-transparent">
|
||||
<input type="radio" id="use-hex-code" name="currency-code-type" />
|
||||
</div>
|
||||
</div>
|
||||
<label for="use-hex-code" class="input-group-text col-lg-3">Non-standard:</label>
|
||||
<input type="text" id="currency-code-hex" pattern="[0-9A-F]{40}" value="015841551A748AD2C1F76FF6ECB0CCCD00000000" title="40 hexadecimal characters" class="form-control col-lg-8" />
|
||||
</div>
|
||||
|
||||
<div class="input-group row mt-4">
|
||||
<label for="trust-limit" class="input-group-text col-lg-3">Limit:</label>
|
||||
<input type="number" id="trust-limit" min="0" value="1000000000" title="Maximum amount the hot address can hold" class="form-control col-lg-9" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="create-trust-line-button" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait (TrustSet)">Create Trust Line</button>
|
||||
<div class="loader collapse"><img class="throbber" src="assets/img/xrp-loader-96.png">Sending transaction...</div>
|
||||
<div class="output-area"></div>
|
||||
{{ end_step() }}
|
||||
|
||||
**Note:** If you use [Authorized Trust Lines][], there is an extra step after this one: the cold address must approve the trust line from the hot address. For details of how to do this, see [Authorizing Trust Lines](authorized-trust-lines.html#authorizing-trust-lines).
|
||||
|
||||
|
||||
### {{n.next()}}. Wait for Validation
|
||||
|
||||
As before, wait for the previous transaction to be validated by consensus before continuing.
|
||||
|
||||
{{ start_step("Wait (TrustSet)") }}
|
||||
{% include '_snippets/interactive-tutorials/wait-step.md' %}
|
||||
{{ end_step() }}
|
||||
|
||||
|
||||
### {{n.next()}}. Send Token
|
||||
|
||||
Now you can create tokens by sending a [Payment transaction][] from the cold address to the hot address. This transaction should have the following attributes (dot notation indicates nested fields):
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| `TransactionType` | `"Payment"` |
|
||||
| `Account` | The cold address issuing the token. |
|
||||
| `Amount` | An [issued currency amount](basic-data-types.html#specifying-currency-amounts) specifying how much of which token to create. |
|
||||
| `Amount.currency` | The currency code of the token. |
|
||||
| `Amount.value` | Decimal amount of the token to issue, as a string. |
|
||||
| `Amount.issuer` | The cold address issuing the token. |
|
||||
| `Destination` | The hot address (or other account receiving the token) |
|
||||
| `Paths` | Omit this field when issuing tokens. |
|
||||
| `SendMax` | Omit this field when issuing tokens. |
|
||||
| `DestinationTag` | Any whole number from 0 to 2<sup>32</sup>-1. You must specify _something_ here if you enabled [Require Destination Tags][] on the hot address. |
|
||||
|
||||
You can use [auto-filled values](transaction-common-fields.html#auto-fillable-fields) for all other required fields.
|
||||
|
||||
The following code sample shows how to send a [Payment transaction][] to issue 88 FOO from the cold address to the hot address:
|
||||
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/js/issue-a-token.js", start_with="// Send token", end_before="// Check balances", language="js") }}
|
||||
|
||||
_Python_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/py/issue-a-token.py", start_with="# Send token", end_before="# Check balances", language="py") }}
|
||||
|
||||
_Java_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/java/IssueToken.java", start_with="// Send token", end_before="// Check balances", language="java") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Send Token") }}
|
||||
<form>
|
||||
<div class="form-inline mt-2">
|
||||
<div class="input-group">
|
||||
<label for="send-amount" class="input-group-text">Send amount:</label>
|
||||
<input type="number" id="send-amount" min="0" value="123.45" step="0.01" title="How much to send to the hot address" class="form-control" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text" id="send-currency-code">FOO</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-inline mt-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text bg-transparent">
|
||||
<input type="checkbox" id="use-dest-tag" class="mr-2" checked="checked" />
|
||||
</div>
|
||||
</div>
|
||||
<label for="dest-tag" class="input-group-text">DestinationTag:</label>
|
||||
<input type="number" id="dest-tag" value="0" min="0" max="4294967295" class="form-control" title="Any 32-bit integer" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<button id="send-token-button" class="btn btn-primary previous-steps-required" data-wait-step-name="Wait (Payment)">Send Token</button>
|
||||
<div class="loader collapse"><img class="throbber" src="assets/img/xrp-loader-96.png">Sending transaction...</div>
|
||||
<div class="output-area"></div>
|
||||
{{ end_step() }}
|
||||
|
||||
|
||||
### {{n.next()}}. Wait for Validation
|
||||
|
||||
As before, wait for the previous transaction to be validated by consensus before continuing.
|
||||
|
||||
{{ start_step("Wait (Payment)") }}
|
||||
{% include '_snippets/interactive-tutorials/wait-step.md' %}
|
||||
{{ end_step() }}
|
||||
|
||||
|
||||
### {{n.next()}}. Confirm Token Balances
|
||||
|
||||
You can check the balances of your token from the perspective of either the token issuer or the hot wallet. Tokens issued in the XRP Ledger always have balances that sum to 0: negative from the perspective of the issuer and positive from the perspective of the holder.
|
||||
|
||||
Use the [account_lines method][] to look up the balances from the perspective of the holder. This lists each trust line along with its limit, balance, and settings.
|
||||
|
||||
Use the [gateway_balances method][] to look up balances from the perspective of a token issuer. This provides a sum of all tokens issued by a given address.
|
||||
|
||||
**Tip:** Since the XRP Ledger is fully public, you can check the balances of any account at any time without needing any cryptographic keys.
|
||||
|
||||
The following code sample shows how to use both methods:
|
||||
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/js/issue-a-token.js", start_with="// Check balances", end_before="// End of", language="js") }}
|
||||
|
||||
_Python_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/py/issue-a-token.py", start_with="# Check balances", language="py") }}
|
||||
|
||||
_Java_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/java/IssueToken.java", start_with="// Check balances", end_before="// Helper", language="java") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Confirm Balances") }}
|
||||
<button id="confirm-balances-button" class="btn btn-primary previous-steps-required">Confirm Balances</button>
|
||||
<div class="loader collapse"><img class="throbber" src="assets/img/xrp-loader-96.png">Checking...</div>
|
||||
<div class="output-area"></div>
|
||||
{{ end_step() }}
|
||||
|
||||
|
||||
### Next Steps
|
||||
|
||||
Now that you've created the token, you can explore how it fits into features of the XRP Ledger:
|
||||
|
||||
- Send tokens from the hot address to other users.
|
||||
- Trade it in the decentralized exchange.
|
||||
- Monitor for incoming payments of your token.
|
||||
- Create an [xrp-ledger.toml file](xrp-ledger-toml.html) and set up domain verification for your token's issuer.
|
||||
- Learn about other [features of XRP Ledger tokens](issued-currencies.html).
|
||||
|
||||
|
||||
## Footnotes
|
||||
|
||||
¹ Users can hold your token without explicitly creating a trust line if they purchase your token in the [decentralized exchange](decentralized-exchange.html). Buying a token in the exchange [automatically creates the necessary trust lines](offers.html#offers-and-trust). This is only possible if someone is selling your token in the decentralized exchange.
|
||||
|
||||
|
||||
<!--{# common link defs #}-->
|
||||
{% include '_snippets/rippled-api-links.md' %}
|
||||
{% include '_snippets/tx-type-links.md' %}
|
||||
{% include '_snippets/rippled_versions.md' %}
|
||||
@@ -62,7 +62,6 @@ default_keys: &defaults
|
||||
# Hover icon markup for permalinking headers
|
||||
hover_anchors: <i class="fa fa-link"></i>
|
||||
# Script tags used in a variety of tools & examples.
|
||||
lodash_tag: '<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>'
|
||||
ripple_lib_tag: '<script type="application/javascript" src="assets/js/ripple-lib-1.10.0.min.js"></script>'
|
||||
|
||||
targets:
|
||||
@@ -155,6 +154,10 @@ targets:
|
||||
"ledger-history.html#full-history": "ledger-history.html#すべての履歴"
|
||||
# Fix link from untranslated amendment-voting.html and rate-limiting.html:
|
||||
"get-started-using-http-websocket-apis.html#admin-access": "get-started-using-http-websocket-apis.html#管理者アクセス権限"
|
||||
# Fix links from untranslated issue-a-fungible-token.html
|
||||
"xrp-ledger-toml.html#account-verification": "xrp-ledger-toml.html#アカウント検証"
|
||||
"offers.html#offers-and-trust": "offers.html#オファーとトラスト"
|
||||
"authorized-trust-lines.html#authorizing-trust-lines": "authorized-trust-lines.html#トラストラインの承認"
|
||||
|
||||
|
||||
pages:
|
||||
@@ -1349,6 +1352,22 @@ pages:
|
||||
targets:
|
||||
- ja
|
||||
|
||||
# TODO: translate title & blurb
|
||||
- name: Use Tokens
|
||||
html: use-tokens.html
|
||||
parent: tutorials.html
|
||||
blurb: Create and trade tokens (fungible or otherwise) in the XRP Ledger.
|
||||
template: pagetype-category.html.jinja
|
||||
targets:
|
||||
- en
|
||||
- ja
|
||||
|
||||
# TODO: translate
|
||||
- md: tutorials/use-tokens/issue-a-fungible-token.md
|
||||
targets:
|
||||
- en
|
||||
- ja
|
||||
|
||||
# TODO: "Send a Cross-Currency Payment"
|
||||
|
||||
# TODO: Set up issuing/operational addresses, monitor changes to balances,
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
padding: 0 2rem 2rem 2rem;
|
||||
}
|
||||
|
||||
.interactive-block .breadcrumbs-wrap {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item:before {
|
||||
content: "\f105"; /* fontawesome angle-right */
|
||||
font-family: FontAwesome;
|
||||
|
||||
@@ -36,6 +36,15 @@ button[disabled="disabled"] {
|
||||
&:hover {
|
||||
background: $blue-purple-600;
|
||||
}
|
||||
|
||||
&.disabled,
|
||||
&[disabled="disabled"] {
|
||||
background: $blue-purple-700;
|
||||
|
||||
&:hover {
|
||||
background: $blue-purple-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-arrow {
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
margin-top: .5rem; // Fix "Getting ready to send..." position
|
||||
}
|
||||
|
||||
.page-tx-sender .input-group .form-control {
|
||||
.page-tx-sender .input-group .form-control,
|
||||
.interactive-block-ui .input-group .form-control {
|
||||
flex: 1 1 20%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@@ -14,40 +14,46 @@
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.interactive-block .breadcrumbs-wrap {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
.interactive-block {
|
||||
input:invalid {
|
||||
box-shadow: inset 0 0 5px 5px $danger;
|
||||
}
|
||||
|
||||
.interactive-block .breadcrumb-item {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.breadcrumbs-wrap {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.interactive-block .breadcrumb-item a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.breadcrumb-item {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.interactive-block .breadcrumb-item.current a {
|
||||
font-weight: bold;
|
||||
}
|
||||
.breadcrumb-item a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.interactive-block .breadcrumb-item.active a {
|
||||
color: $blue-purple-300;
|
||||
}
|
||||
.breadcrumb-item.current a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.interactive-block .breadcrumb-item.disabled a {
|
||||
color: $gray-600;
|
||||
}
|
||||
.breadcrumb-item.active a {
|
||||
color: $blue-purple-300;
|
||||
}
|
||||
|
||||
.interactive-block .breadcrumb-item.done a:after {
|
||||
content: "\f058"; /* fontawesome check-circle icon */
|
||||
font-family: FontAwesome;
|
||||
color: $secondary;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
.breadcrumb-item.disabled a {
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
.interactive-block .waiting-for-tx {
|
||||
word-break: break-word;
|
||||
.breadcrumb-item.done a:after {
|
||||
content: "\f058"; /* fontawesome check-circle icon */
|
||||
font-family: FontAwesome;
|
||||
color: $secondary;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.waiting-for-tx {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.ws-console {
|
||||
|
||||
@@ -156,7 +156,6 @@ $(document).ready(() => {
|
||||
|
||||
{% if currentpage.embed_ripple_lib %}
|
||||
<!-- ripple-lib & prerequisites -->
|
||||
{{currentpage.lodash_tag}}
|
||||
{{currentpage.ripple_lib_tag}}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block endbody %}
|
||||
{{currentpage.lodash_tag}}
|
||||
{{currentpage.ripple_lib_tag}}
|
||||
<script type='text/javascript' src='assets/js/rpc-tool.js'></script>
|
||||
|
||||
|
||||
@@ -18,6 +18,33 @@ Interactive Tutorials are intended to demonstrate interactivity with the XRP Led
|
||||
|
||||
- As a best practice, don't call the faucet unless the user interacts with the page by clicking a button or something. Otherwise, web crawlers and things just loading the page end up draining the faucet pretty quickly.
|
||||
|
||||
## Memos
|
||||
|
||||
Transactions sent from interactive tutorials automatically attach a memo indicating what button of which tutorial sent the memo. Anyone who is watching Testnet/Devnet transactions can look for these memos to see when people are using the tutorials.
|
||||
|
||||
The memo is identified by a `MemoType` that decodes to the URL of this document:
|
||||
|
||||
```
|
||||
MemoType (hex):
|
||||
68747470733A2F2F6769746875622E636F6D2F5852504C462F7872706C2D6465762D706F7274616C2F626C6F622F6D61737465722F746F6F6C2F494E5445524143544956455F5455544F5249414C535F524541444D452E6D64
|
||||
|
||||
MemoType (ASCII-decoded):
|
||||
https://github.com/XRPLF/xrpl-dev-portal/blob/master/tool/INTERACTIVE_TUTORIALS_README.md
|
||||
```
|
||||
|
||||
The memo has a `MemoFormat` value of `6170706C69636174696F6E2F6A736F6E` (hex), which represents the MIME type `application/json`.
|
||||
|
||||
The memo has a `MemoData` field which is ASCII-encoded JSON containing the following data:
|
||||
|
||||
| Field | Type | Contents |
|
||||
|---|---|---|
|
||||
| `path` | String | The `window.location.pathname` of the tutorial. For example, `/send-xrp.html`. |
|
||||
| `button` | String | The unique html ID of the button that triggered this transaction. For example, `submit-button`. |
|
||||
|
||||
For privacy reasons, the memo does not and MUST NOT include personally identifying information about the user or their browser.
|
||||
|
||||
**Note:** The interactive tutorial code assumes that the path and ID are both possible to encode with plain ASCII, so please avoid using non-ASCII characters in the IDs and filenames.
|
||||
|
||||
## Recommended Process
|
||||
|
||||
An interactive tutorial is a page, so you add it to the `dactyl-config.yml` page like any other page. However, you need to add the following pieces to make the interactive stuff work:
|
||||
|
||||
@@ -23,6 +23,7 @@ base58check
|
||||
base64
|
||||
blockchains
|
||||
cmake
|
||||
codebases
|
||||
cors
|
||||
counterparty
|
||||
counterparties
|
||||
@@ -49,6 +50,8 @@ mainnet
|
||||
micropayments
|
||||
middleware
|
||||
multisigning
|
||||
NFT
|
||||
NFTs
|
||||
noripple
|
||||
nudb
|
||||
p2p
|
||||
@@ -64,8 +67,12 @@ rtxp
|
||||
secp256k1
|
||||
semver
|
||||
shor
|
||||
sidechain
|
||||
sidechains
|
||||
SSDs
|
||||
SSL
|
||||
stablecoin
|
||||
stablecoins
|
||||
statsd
|
||||
testnet
|
||||
TLS
|
||||
@@ -76,6 +83,7 @@ v1
|
||||
v2
|
||||
v10
|
||||
validators
|
||||
XRPL
|
||||
|
||||
# rippled API methods
|
||||
account_channels
|
||||
@@ -201,6 +209,7 @@ ticksize
|
||||
trustsetauth
|
||||
|
||||
# Names of people, orgs, etc.
|
||||
AWS
|
||||
bithomp
|
||||
britto
|
||||
macbrough
|
||||
@@ -208,6 +217,7 @@ RippleX
|
||||
xpring
|
||||
xrpcharts
|
||||
xrpchat
|
||||
Xrplorer
|
||||
xrpscan
|
||||
xrpl4j
|
||||
XRPLF
|
||||
|
||||
Reference in New Issue
Block a user