mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-19 19:25:51 +00:00
Issue a token: working code
- Fix bugs in submit and verify helper function - Tweak tutorial and placement of helper function - Finish drafting sample code - Put sample code into tutorial
This commit is contained in:
12
content/_code-samples/issue-a-token/demo.html
Normal file
12
content/_code-samples/issue-a-token/demo.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Code Sample - Issue a Token</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.8/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>
|
||||
@@ -1,12 +1,7 @@
|
||||
// Example credentials
|
||||
let hot_address = "rMCcNuTcajgw7YTgBy1sys3b89QqjUrMpH"
|
||||
let hot_secret = "sn3nxiW7v8KXzPzAqzyHXbSSKNuN9"
|
||||
let cold_address = ""
|
||||
let cold_secret = ""
|
||||
|
||||
// Connect ---------------------------------------------------------------------
|
||||
// ripple = require('ripple-lib') // For Node.js. In browsers, use <script>.
|
||||
api = new ripple.RippleAPI({server: 'wss://s.altnet.rippletest.net:51233'})
|
||||
console.log("Connecting to Testnet...")
|
||||
api.connect()
|
||||
api.on('connected', async () => {
|
||||
|
||||
@@ -30,13 +25,14 @@ api.on('connected', async () => {
|
||||
return data
|
||||
}
|
||||
|
||||
console.log("Requesting addresses from the Testnet faucet...")
|
||||
const hot_data = await get_faucet_address()
|
||||
hot_address = hot_data.account.address
|
||||
hot_secret = hot_data.account.secret
|
||||
const hot_address = hot_data.account.address
|
||||
const hot_secret = hot_data.account.secret
|
||||
|
||||
const cold_data = await get_faucet_address()
|
||||
cold_address = cold_data.account.address
|
||||
cold_secret = cold_data.account.secret
|
||||
const cold_address = cold_data.account.address
|
||||
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
|
||||
@@ -45,31 +41,120 @@ api.on('connected', async () => {
|
||||
// the faucet.
|
||||
while (true) {
|
||||
try {
|
||||
await api.request("account_info", {account: address, ledger_index: "validated"})
|
||||
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}.`)
|
||||
|
||||
// Prepare AccountSet transaction for the issuer (cold address)
|
||||
// Configure issuer (cold address) settings ----------------------------------
|
||||
const cold_settings_tx = {
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": cold_address,
|
||||
"TransferFee": 0,
|
||||
"TransferRate": 0,
|
||||
"TickSize": 5,
|
||||
"SetFlag": 8 // enable Default Ripple
|
||||
//"Flags": (api.txFlags.AccountSet.DisallowXRP |
|
||||
// api.txFlags.AccountSet.RequireDestTag)
|
||||
}
|
||||
|
||||
const prepared_cst = await api.prepareTransaction(cold_settings_tx, {maxLedgerVersionOffset: 10})
|
||||
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,
|
||||
"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 payment_prepared = await api.prepareTransaction(send_token_tx, {maxLedgerVersionOffset: 10})
|
||||
const payment_signed = api.sign(payment_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 payment_result = await submit_and_verify(api, payment_signed.signedTransaction)
|
||||
if (payment_result == "tesSUCCESS") {
|
||||
console.log(`Transaction succeeded: https://testnet.xrpl.org/transactions/${payment_signed.id}`)
|
||||
} else {
|
||||
throw `Error sending transaction: ${payment_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(cold_balances)
|
||||
|
||||
}) // End of api.on.('connected')
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<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 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>
|
||||
|
||||
@@ -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);
|
||||
@@ -5,7 +5,7 @@
|
||||
<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 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>
|
||||
|
||||
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}`)
|
||||
}
|
||||
}
|
||||
```
|
||||
167
content/_code-samples/submit-and-verify/submit-and-verify.js
Normal file
167
content/_code-samples/submit-and-verify/submit-and-verify.js
Normal file
@@ -0,0 +1,167 @@
|
||||
// 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:", prelim_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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ blurb: Create your own token and issue it on the XRP Ledger Testnet.
|
||||
embed_ripple_lib: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
- include_code
|
||||
labels:
|
||||
- Tokens
|
||||
---
|
||||
@@ -113,9 +114,14 @@ Other settings you may want to, optionally, configure for your cold address (iss
|
||||
|
||||
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended cold address settings:
|
||||
|
||||
```
|
||||
TODO: CODE SAMPLE
|
||||
```
|
||||
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Configure issuer", end_before="// Configure hot", language="js") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Configure Issuer") }}
|
||||
<form>
|
||||
@@ -176,10 +182,13 @@ The hot address does not strictly require any settings changes from the default,
|
||||
|
||||
The following code sample shows how to send an [AccountSet transaction][] to enable the recommended hot address settings:
|
||||
|
||||
```
|
||||
TODO: code
|
||||
```
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Configure hot address", end_before="// Create trust line", language="js") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Configure Hot Address") }}
|
||||
<form>
|
||||
@@ -205,9 +214,13 @@ The hot address needs a trust line like this before it can receive tokens from t
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
TODO: code
|
||||
```
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Create trust line", end_before="// Send token", language="js") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Make Trust Line") }}
|
||||
<form>
|
||||
@@ -259,9 +272,13 @@ You can use [auto-filled values](transaction-common-fields.html#auto-fillable-fi
|
||||
|
||||
The following code sample shows how to send a [Payment transaction][] to issue 88 FOO from the cold address to the hot address:
|
||||
|
||||
```
|
||||
TODO: code
|
||||
```
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Send token", end_before="// Check balances", language="js") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Send Token") }}
|
||||
<button id="send-token-button" class="btn btn-primary">Send Token</button>
|
||||
@@ -282,9 +299,13 @@ Use the [gateway_balances method][] to look up balances from the perspective of
|
||||
|
||||
The following code sample shows how to use both methods:
|
||||
|
||||
```
|
||||
TODO: code
|
||||
```
|
||||
<!-- MULTICODE_BLOCK_START -->
|
||||
|
||||
_JavaScript_
|
||||
|
||||
{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Check balances", end_before="// End of", language="js") }}
|
||||
|
||||
<!-- MULTICODE_BLOCK_END -->
|
||||
|
||||
{{ start_step("Confirm Balances") }}
|
||||
<button id="check-balances-button" class="btn btn-primary">Confirm Balances</button>
|
||||
|
||||
Reference in New Issue
Block a user