diff --git a/content/_code-samples/issue-a-token/demo.html b/content/_code-samples/issue-a-token/demo.html new file mode 100644 index 0000000000..0f7a15174b --- /dev/null +++ b/content/_code-samples/issue-a-token/demo.html @@ -0,0 +1,12 @@ + + + + +Code Sample - Issue a Token + + + + + +Open your browser's console (F12) to see the logs. + diff --git a/content/_code-samples/issue-a-token/issue-a-token.js b/content/_code-samples/issue-a-token/issue-a-token.js index 6e712808fa..ceae5a0fcf 100644 --- a/content/_code-samples/issue-a-token/issue-a-token.js +++ b/content/_code-samples/issue-a-token/issue-a-token.js @@ -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 - + Open your browser's console (F12) to see the logs. diff --git a/content/_code-samples/rippleapi_quickstart/semi-reliable-submit.js b/content/_code-samples/rippleapi_quickstart/semi-reliable-submit.js deleted file mode 100644 index 3a80a13ff9..0000000000 --- a/content/_code-samples/rippleapi_quickstart/semi-reliable-submit.js +++ /dev/null @@ -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 -} diff --git a/content/_code-samples/rippleapi_quickstart/submit-and-verify.js b/content/_code-samples/rippleapi_quickstart/submit-and-verify.js deleted file mode 100644 index b243bfee97..0000000000 --- a/content/_code-samples/rippleapi_quickstart/submit-and-verify.js +++ /dev/null @@ -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); diff --git a/content/_code-samples/send-xrp/demo.html b/content/_code-samples/send-xrp/demo.html index f2cef38026..c397d71002 100644 --- a/content/_code-samples/send-xrp/demo.html +++ b/content/_code-samples/send-xrp/demo.html @@ -5,7 +5,7 @@ Code Sample - Send XRP - + Open your browser's console (F12) to see the logs. diff --git a/content/_code-samples/submit-and-verify/README.md b/content/_code-samples/submit-and-verify/README.md new file mode 100644 index 0000000000..ccbb5440e3 --- /dev/null +++ b/content/_code-samples/submit-and-verify/README.md @@ -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}`) + } +} +``` diff --git a/content/_code-samples/submit-and-verify/submit-and-verify.js b/content/_code-samples/submit-and-verify/submit-and-verify.js new file mode 100644 index 0000000000..668455c16a --- /dev/null +++ b/content/_code-samples/submit-and-verify/submit-and-verify.js @@ -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 -> 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; +} diff --git a/content/tutorials/get-started/get-started-using-node-js.ja.md b/content/tutorials/get-started/get-started-using-node-js.ja.md index a5e6d8a583..ffe81b1c82 100644 --- a/content/tutorials/get-started/get-started-using-node-js.ja.md +++ b/content/tutorials/get-started/get-started-using-node-js.ja.md @@ -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パラメーターの値よりも大きくなるまで、このプロセスを繰り返します。 diff --git a/content/tutorials/get-started/get-started-using-node-js.md b/content/tutorials/get-started/get-started-using-node-js.md index c63e40909f..55ffcec184 100644 --- a/content/tutorials/get-started/get-started-using-node-js.md +++ b/content/tutorials/get-started/get-started-using-node-js.md @@ -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. diff --git a/content/tutorials/use-tokens/issue-a-fungible-token.md b/content/tutorials/use-tokens/issue-a-fungible-token.md index 0317f4134c..84e940c286 100644 --- a/content/tutorials/use-tokens/issue-a-fungible-token.md +++ b/content/tutorials/use-tokens/issue-a-fungible-token.md @@ -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 -``` + + + +_JavaScript_ + +{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Configure issuer", end_before="// Configure hot", language="js") }} + + {{ start_step("Configure Issuer") }}
@@ -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 -``` + +_JavaScript_ + +{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Configure hot address", end_before="// Create trust line", language="js") }} + + {{ start_step("Configure Hot Address") }} @@ -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 -``` + + +_JavaScript_ + +{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Create trust line", end_before="// Send token", language="js") }} + + {{ start_step("Make Trust Line") }} @@ -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 -``` + + +_JavaScript_ + +{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Send token", end_before="// Check balances", language="js") }} + + {{ start_step("Send Token") }} @@ -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 -``` + + +_JavaScript_ + +{{ include_code("_code-samples/issue-a-token/issue-a-token.js", start_with="// Check balances", end_before="// End of", language="js") }} + + {{ start_step("Confirm Balances") }}