diff --git a/content/code_samples/rippleapi_quickstart/eslintrc b/content/code_samples/rippleapi_quickstart/eslintrc new file mode 100644 index 0000000000..8282b721b8 --- /dev/null +++ b/content/code_samples/rippleapi_quickstart/eslintrc @@ -0,0 +1,181 @@ +# ESLint documentation can be found at http://eslint.org/docs/ +env: + browser: true + node: true + amd: false + mocha: true + jasmine: false +rules: + no-alert: 2 + no-array-constructor: 2 + no-bitwise: 0 + no-caller: 2 + no-catch-shadow: 2 + comma-dangle: 2 + no-class-assign: 2 + no-cond-assign: [2, 'always'] + no-console: 0 + no-const-assign: 2 + no-constant-condition: 2 + no-control-regex: 2 + no-debugger: 2 + no-delete-var: 2 + no-div-regex: 0 + no-dupe-keys: 2 + no-dupe-args: 2 + no-duplicate-case: 2 + no-else-return: 2 + no-empty: 2 + no-empty-character-class: 2 + no-empty-label: 2 + no-eq-null: 2 + no-eval: 2 + no-ex-assign: 2 + no-extend-native: 2 + no-extra-bind: 2 + no-extra-boolean-cast: 2 + no-extra-parens: [2, 'functions'] + no-extra-semi: 2 + no-fallthrough: 2 + no-floating-decimal: 0 + no-func-assign: 2 + no-implicit-coercion: 2 + no-implied-eval: 2 + no-inline-comments: 0 + no-inner-declarations: [2, 'functions'] + no-invalid-regexp: 2 + no-irregular-whitespace: 2 + no-iterator: 2 + no-label-var: 2 + no-labels: 2 + no-lone-blocks: 2 + no-lonely-if: 2 + no-loop-func: 2 + no-mixed-requires: [0, false] + no-mixed-spaces-and-tabs: [2, false] + no-multi-spaces: 2 + no-multi-str: 2 + no-multiple-empty-lines: [2, {max: 2}] + no-native-reassign: 2 + no-negated-in-lhs: 2 + no-nested-ternary: 0 + no-new: 2 + no-new-func: 2 + no-new-object: 2 + no-new-require: 0 + no-new-wrappers: 2 + no-obj-calls: 2 + no-octal: 2 + no-octal-escape: 2 + no-param-reassign: 2 + no-path-concat: 0 + no-plusplus: 0 + no-process-env: 0 + no-process-exit: 0 + no-proto: 2 + no-redeclare: 2 + no-regex-spaces: 2 + no-restricted-modules: 0 + no-return-assign: 2 + no-script-url: 2 + no-self-compare: 2 + no-sequences: 2 + no-shadow: 2 + no-shadow-restricted-names: 2 + semi-spacing: 2 + no-spaced-func: 2 + no-sparse-arrays: 2 + no-sync: 0 + no-ternary: 0 + no-trailing-spaces: 2 + no-undef: 2 + no-undef-init: 2 + no-undefined: 0 + no-underscore-dangle: 0 + no-unreachable: 2 + no-unused-expressions: 2 + no-unused-vars: [2, {vars: 'all', args: 'all'}] + no-use-before-define: 2 + no-void: 2 + no-var: 2 + prefer-const: 2 + no-warning-comments: [0, {terms: ['todo', 'fixme', 'xxx'], location: 'start'}] + no-with: 2 + block-scoped-var: 2 + brace-style: 2 + camelcase: 0 + comma-spacing: 2 + comma-style: 2 + complexity: [0, 11] + consistent-return: 2 + consistent-this: [2, 'self'] + curly: [2, 'all'] + default-case: 0 + dot-notation: [2, {allowKeywords: true}] + eol-last: 2 + eqeqeq: 2 + func-names: 0 + func-style: [2, 'declaration'] + generator-star: 0 + guard-for-in: 0 + handle-callback-err: 2 + indent: [2, 2, {SwitchCase: 1}] + key-spacing: [2, {beforeColon: false, afterColon: true}] + max-depth: [1, 4] + max-len: [2, 80] + max-nested-callbacks: [1, 2] + max-params: [1, 4] + max-statements: [0, 10] + new-cap: 2 + new-parens: 2 + one-var: [2, 'never'] + operator-assignment: [0, 'always'] + padded-blocks: 0 + quote-props: 0 + quotes: [2, 'single'] + radix: 2 + semi: 2 + sort-vars: 0 + space-after-keywords: 2 + space-before-blocks: 2 + space-before-function-paren: [2, 'never'] + object-curly-spacing: [2, 'never'] + array-bracket-spacing: [2, 'never'] + space-in-parens: 2 + space-infix-ops: 2 + space-return-throw-case: 2 + space-unary-ops: [2, {words: true, nonwords: false}] + spaced-comment: 2 + strict: [2, 'global'] + use-isnan: 2 + valid-jsdoc: 2 + valid-typeof: 2 + vars-on-top: 0 + wrap-iife: 0 + wrap-regex: 0 + yoda: [2, 'never'] +ecmaFeatures: + arrowFunctions: true + binaryLiterals: true + blockBindings: true + classes: true + defaultParams: true + destructuring: true + forOf: true + generators: true +# not sure about the implications of globalReturn +# globalReturn: true + jsx: true + objectLiteralComputedProperties: true + objectLiteralShorthandMethods: true + objectLiteralShorthandProperties: true +# duplicate properties may be required but we are not +# sure at this point +# objectLiteralDuplicateProperties: true + octalLiterals: true + regexUFlag: true + regexYFlag: true + restParams: true + spread: true + templateStrings: true + unicodeCodePointEscapes: true diff --git a/content/code_samples/rippleapi_quickstart/get-account-info.js b/content/code_samples/rippleapi_quickstart/get-account-info.js index 6a78a4663e..380bdb5e91 100644 --- a/content/code_samples/rippleapi_quickstart/get-account-info.js +++ b/content/code_samples/rippleapi_quickstart/get-account-info.js @@ -1,22 +1,24 @@ 'use strict'; const {RippleAPI} = require('ripple-lib'); - + const api = new RippleAPI({ server: 'wss://s1.ripple.com' // Public rippled server }); api.connect().then(() => { /* begin custom code ------------------------------------ */ - var my_address = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; - - console.log("getting account info for",my_address); - api.getAccountInfo(my_address).then( info => { - console.log(info); - console.log("getAccountInfo done"); - } ).catch(console.error); - + const my_address = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn'; + + console.log('getting account info for', my_address); + return api.getAccountInfo(my_address); + +// info => {...} is just a shorter syntax for function(info) {...} +}).then(info => { + console.log(info); + console.log('getAccountInfo done'); + /* end custom code -------------------------------------- */ }).then(() => { - return api.disconnect().then(()=> { - console.log("done and disconnected."); - }).catch(console.error); + return api.disconnect(); +}).then(()=> { + console.log('done and disconnected.'); }).catch(console.error); diff --git a/content/code_samples/rippleapi_quickstart/submit-and-verify.js b/content/code_samples/rippleapi_quickstart/submit-and-verify.js new file mode 100644 index 0000000000..7dea9fd8da --- /dev/null +++ b/content/code_samples/rippleapi_quickstart/submit-and-verify.js @@ -0,0 +1,93 @@ +'use strict'; +/* import RippleAPI and support libraies*/ +const {RippleAPI} = require('ripple-lib'); +const assert = require('assert'); + +/* Credentials of the account placing the offer */ +const my_addr = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn'; +const my_secret = 's████████████████████████████'; + +/* Define the order to place here */ +const my_order = { + 'direction': 'buy', + 'quantity': { + 'currency': '', + 'counterparty': '', + 'value': '' + }, + 'totalPrice': { + 'currency': '', + 'counterparty': '', + 'value': '' + } +}; + +/* milliseconds to wait between ledger checks*/ +const INTERVAL = 1000; +/* Instantiate RippleAPI */ +const api = new RippleAPI({server: 'wss://s1.ripple.com'}); +/* number of ledgers to check for valid transaction before fail */ +const ledgerOffset = 5; +const my_instructions = {maxLedgerVersionOffset: ledgerOffset}; + + +/* function to verify a transaction is on the RCL */ +function verifyTransaction(hash, options) { + console.log('Verifing Transaction'); + return api.getTransaction(hash, options).then(data => { + console.log('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 on current 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 result;// TODO: Fix this. It's currently undefined. + }); +} + + +/* function to prepare, sign, and submit a transaction to the RCL +success verifies the transaction is being considered for the next ledger. +Still requires vlaidation */ +function submitTransaction(lastClosedLedgerVersion, prepared, secret) { + const signedData = api.sign(prepared.txJSON, secret); + return api.submit(signedData.signedTransaction).then(data => { + console.log('Result: ', data.resultCode); + console.log('Message: ', data.resultMessage); + /* if transaction was not successfully submitted throw error */ + assert.strictEqual(data.resultCode, 'tesSUCCESS'); + /* if successfully submitted fire off 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(my_addr, my_order, my_instructions); +}).then(prepared => { + console.log('Order Prepared'); + return api.getLedger().then(ledger => { + console.log('Current Ledger', ledger.ledgerVersion); + return submitTransaction(ledger.ledgerVersion, prepared, my_secret); + }); +}).then(() => { + api.disconnect().then(() => { + console.log('api disconnected'); + process.exit(); + }); +}).catch(console.error); + diff --git a/content/rippleapi_quickstart.md b/content/rippleapi_quickstart.md index bb8560029e..6073f3f715 100644 --- a/content/rippleapi_quickstart.md +++ b/content/rippleapi_quickstart.md @@ -44,23 +44,7 @@ Alternatively, you can [create a repo on GitHub](https://help.github.com/article Here's a good template: ``` -{ - "name": "my_ripple_experiment", - "version": "0.0.1", - "license": "UNLICENSED", - "private": true, - "dependencies": { - "ripple-lib": "*", - "babel-cli": "^6.0.0", - "babel-preset-es2015": "*" - }, - "babel": { - "presets": ["es2015"] - }, - "devDependencies": { - "eslint": "*" - } -} +{% include 'code_samples/rippleapi_quickstart/package.json' %} ``` This includes RippleAPI itself (`ripple-lib`), Babel (`babel-cli`), the ECMAScript 6 presets for Babel (`babel-preset-es2015`). It also has the optional add-on [ESLint](http://eslint.org/) (`eslint`) for checking your code quality. @@ -96,8 +80,126 @@ git add .gitignore git commit -m "ignore node_modules" ``` -# First RippleAPI Script +# First RippleAPI Script ## +With RippleAPI installed, it's time to test that it works. Here's a simple script that uses RippleAPI to retrieve information on a specific account: +``` +{% include 'code_samples/rippleapi_quickstart/get-account-info.js' %} +``` +## Running the script ## +RippleAPI and the script both use the ECMAScript 6 version of JavaScript, which is (at this time) not supported by Node.js natively. That's why we installed Babel earlier. The easiest way to run ECMAScript 6 is to use the `babel-node` binary, which NPM installs in the `node_modules/.bin/` directory of your project. Thus, running the script looks like this: + +``` +./node_modules/.bin/babel-node get-account-info.js +``` + +Output: + +``` +getting account info for rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn +{ sequence: 359, + xrpBalance: '75.181663', + ownerCount: 4, + previousInitiatedTransactionID: 'E5C6DD25B2DCF534056D98A2EFE3B7CFAE4EBC624854DE3FA436F733A56D8BD9', + previousAffectingTransactionID: 'E5C6DD25B2DCF534056D98A2EFE3B7CFAE4EBC624854DE3FA436F733A56D8BD9', + previousAffectingTransactionLedgerVersion: 18489336 } +getAccountInfo done +done and disconnected. +``` + +## Understanding the script ## + +Even for a simple script, there's a lot packed into that, including quite a few conventions that are part of standard JavaScript. Understanding these concepts will help you write better code using RippleAPI, so let's divide the sample code into smaller chunks that are easier to understand. + +### Script opening ### + +``` +'use strict'; +const {RippleAPI} = require('ripple-lib'); +``` + +The opening line enables [strict mode](https://www.nczonline.net/blog/2012/03/13/its-time-to-start-using-javascript-strict-mode/). This is purely optional, but it helps you avoid some common pitfalls of JavaScript. See also: [Restrictions on Code in Strict Mode](https://msdn.microsoft.com/library/br230269%28v=vs.94%29.aspx#Anchor_1). + +The second line imports RippleAPI into the current scope using Node.js's require function. The [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) assigns it to the variable name `RippleAPI` instead of `ripple-lib` (which is the name of the package, for historical reasons). + +### Instantiating the API ### + +``` +const api = new RippleAPI({ + server: 'wss://s1.ripple.com' // Public rippled server +}); +``` + +This section creates a new instance of the RippleAPI class, assigning it to the variable `api`. (The [`const` keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const) means you can't reassign the value `api` to some other value. The internal state of the object can still change, though.) + +The one argument to the constructor is an options object, which has [a variety of options](rippleapi.html#parameters). The `server` parameter tells it where it should connect to a `rippled` server. + +* The example `server` setting uses a secure WebSocket connection to connect to one of the public servers that Ripple (the company) operates. +* If you don't include the `server` option, RippleAPI runs in [offline mode](rippleapi.html#offline-functionality) instead, which severely limits what you can do with it. +* You can specify a [Ripple Test Net](https://ripple.com/build/ripple-test-net/) server instead to connect to the parallel-world Test Network instead of the production Ripple Consensus Ledger. +* If you [run your own `rippled`](rippled-setup.html), you can instruct it to connect to your local server. For example, you might say `server: 'ws://localhost:5005'` instead. + +### Connecting and Promises ### + +``` +api.connect().then(() => { +``` + +The [connect() method](rippleapi.html#connect) is one of many RippleAPI methods that returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise), which is a special kind of JavaScript object. A Promise is designed to perform some asynchronous operation, like querying a the Ripple Consensus Ledger. + +When you get a Promise back from some expression (like `api.connect()`), you call the Promise's `then` method and pass in a callback function. Passing a function as an argument is conventional in JavaScript, taking advantage of how JavaScript functions are [first-class objects](https://en.wikipedia.org/wiki/First-class_function). + +When a Promise finishes with its asynchronous operations, the Promise runs the callback function you passed it. The return value from the `then` method is another Promise object, so you can "chain" that into another `then` method, or the Promise's `catch` method, which also takes a callback. The callback you provide to `catch` gets called if something goes wrong. + +Finally, we have more new ECMAScript 6 syntax - an [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions). Arrow functions are just a shorter way of defining anonymous functions, which is pretty convenient when you're defining lots of one-off functions as callbacks, like we are here. The syntax `()=> {...}` is mostly equivalent to `function() {...}`. If you want an anonymous function with one parameter, you can use a syntax like `info => {...}` instead, which is basically just `function(info) {...}` as well. + +### Custom code ### + +``` + /* begin custom code ------------------------------------ */ + const my_address = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn'; + + console.log('getting account info for',my_address); + return api.getAccountInfo(my_address); + +}).then( info => { + console.log(info); + console.log('getAccountInfo done'); + + /* end custom code -------------------------------------- */ +``` + +This is the part that really defines what this script does, so this is the part you will probably spend the most time customizing. + +The example code looks up a Ripple account (belonging to [this writer](https://github.com/mDuo13/)) by its address. You can substitute your own address, if you want. + +The `console.log()` function is a built-in tool in both Node.js and web browsers, which writes out to the console; this example includes lots of console output to make it easier to understand what the code is doing. + +Keep in mind that the example code starts in the middle of a callback function (called when RippleAPI finishes connecting). That function calls RippleAPI's [`getAccountInfo`](rippleapi.html#getaccountinfo) method, and returns the results. + +The results of that API method are another Promise, so the line `}).then( info => {` passes in another anonymous callback function to run when the second Promise's asynchronous work is done. Unlike the previous case, this callback function takes one argument, called `info`, which holds the -- actual -- return value from the `getAccountInfo` API method. The rest of this callback function just outputs that return value to the console. + +### Cleanup ### + +``` +}).then(() => { + return api.disconnect(); +}).then(()=> { + console.log('done and disconnected.'); +}).catch(console.error); +``` + +The remainder of the sample code is mostly more [boilerplate code](rippleapi.html#boilerplate). The first line ends the previous callback function, then chains to another callback to run when it ends. That method disconnects cleanly from the Ripple Consensus Ledger, and has yet another callback which writes to the console when it finishes. + +Finally, we get to the `catch` method of this entire long Promise chain. If any of the Promises or their callback functions encounters an error, the callback provided here will run. One thing worth noting: instead of defining a new anonymous callback function here, we can just pass in the standard `console.error` function, which writes whatever arguments it gets out to the console. If you so desired, you could define a smarter callback function here which might intelligently catch certain error types. + +# Waiting for Validation # + +One of the biggest challenges in using the Ripple Consensus Ledger (or any decentralized system) is knowing the final, immutable transaction results. Even if you [follow the best practices](reliable_tx.html) you still have to wait for the [consensus process](https://ripple.com/knowledge_center/the-ripple-ledger-consensus-process/) to finally accept or reject your transaction. The following example code demonstrates how to wait for the final outcome of a transaction: + +``` +{% include 'code_samples/rippleapi_quickstart/submit-and-verify.js' %} +``` diff --git a/rippleapi_quickstart.html b/rippleapi_quickstart.html index 9158170879..e0d768ca0c 100644 --- a/rippleapi_quickstart.html +++ b/rippleapi_quickstart.html @@ -159,8 +159,9 @@
{
   "name": "my_ripple_experiment",
   "version": "0.0.1",
-  "license": "UNLICENSED",
+  "license": "MIT",
   "private": true,
+  "//": "Change the license to something appropriate. You may want to use 'UNLICENSED' if you are just starting out.",
   "dependencies": {
     "ripple-lib": "*",
     "babel-cli": "^6.0.0",
@@ -193,6 +194,198 @@ npm WARN ajv@1.4.10 requires a peer of ajv-i18n@0.1.x but none was installed.
 git commit -m "ignore node_modules"
 

First RippleAPI Script

+

With RippleAPI installed, it's time to test that it works. Here's a simple script that uses RippleAPI to retrieve information on a specific account:

+
'use strict';
+const {RippleAPI} = require('ripple-lib');
+
+const api = new RippleAPI({
+  server: 'wss://s1.ripple.com' // Public rippled server
+});
+api.connect().then(() => {
+  /* begin custom code ------------------------------------ */
+  const my_address = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn';
+
+  console.log('getting account info for', my_address);
+  return api.getAccountInfo(my_address);
+
+// info => {...} is just a shorter syntax for function(info) {...}
+}).then(info => {
+  console.log(info);
+  console.log('getAccountInfo done');
+
+  /* end custom code -------------------------------------- */
+}).then(() => {
+  return api.disconnect();
+}).then(()=> {
+  console.log('done and disconnected.');
+}).catch(console.error);
+
+

Running the script

+

RippleAPI and the script both use the ECMAScript 6 version of JavaScript, which is (at this time) not supported by Node.js natively. That's why we installed Babel earlier. The easiest way to run ECMAScript 6 is to use the babel-node binary, which NPM installs in the node_modules/.bin/ directory of your project. Thus, running the script looks like this:

+
./node_modules/.bin/babel-node get-account-info.js
+
+

Output:

+
getting account info for rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn
+{ sequence: 359,
+  xrpBalance: '75.181663',
+  ownerCount: 4,
+  previousInitiatedTransactionID: 'E5C6DD25B2DCF534056D98A2EFE3B7CFAE4EBC624854DE3FA436F733A56D8BD9',
+  previousAffectingTransactionID: 'E5C6DD25B2DCF534056D98A2EFE3B7CFAE4EBC624854DE3FA436F733A56D8BD9',
+  previousAffectingTransactionLedgerVersion: 18489336 }
+getAccountInfo done
+done and disconnected.
+
+

Understanding the script

+

Even for a simple script, there's a lot packed into that, including quite a few conventions that are part of standard JavaScript. Understanding these concepts will help you write better code using RippleAPI, so let's divide the sample code into smaller chunks that are easier to understand.

+

Script opening

+
'use strict';
+const {RippleAPI} = require('ripple-lib');
+
+

The opening line enables strict mode. This is purely optional, but it helps you avoid some common pitfalls of JavaScript. See also: Restrictions on Code in Strict Mode.

+

The second line imports RippleAPI into the current scope using Node.js's require function. The destructuring assignment assigns it to the variable name RippleAPI instead of ripple-lib (which is the name of the package, for historical reasons).

+

Instantiating the API

+
const api = new RippleAPI({
+  server: 'wss://s1.ripple.com' // Public rippled server
+});
+
+

This section creates a new instance of the RippleAPI class, assigning it to the variable api. (The const keyword means you can't reassign the value api to some other value. The internal state of the object can still change, though.)

+

The one argument to the constructor is an options object, which has a variety of options. The server parameter tells it where it should connect to a rippled server.

+ +

Connecting and Promises

+
api.connect().then(() => {
+
+

The connect() method is one of many RippleAPI methods that returns a Promise, which is a special kind of JavaScript object. A Promise is designed to perform some asynchronous operation, like querying a the Ripple Consensus Ledger.

+

When you get a Promise back from some expression (like api.connect()), you call the Promise's then method and pass in a callback function. Passing a function as an argument is conventional in JavaScript, taking advantage of how JavaScript functions are first-class objects.

+

When a Promise finishes with its asynchronous operations, the Promise runs the callback function you passed it. The return value from the then method is another Promise object, so you can "chain" that into another then method, or the Promise's catch method, which also takes a callback. The callback you provide to catch gets called if something goes wrong.

+

Finally, we have more new ECMAScript 6 syntax - an arrow function. Arrow functions are just a shorter way of defining anonymous functions, which is pretty convenient when you're defining lots of one-off functions as callbacks, like we are here. The syntax ()=> {...} is mostly equivalent to function() {...}. If you want an anonymous function with one parameter, you can use a syntax like info => {...} instead, which is basically just function(info) {...} as well.

+

Custom code

+
  /* begin custom code ------------------------------------ */
+  const my_address = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn';
+
+  console.log('getting account info for',my_address);
+  return api.getAccountInfo(my_address);
+
+}).then( info => {
+  console.log(info);
+  console.log('getAccountInfo done');
+
+  /* end custom code -------------------------------------- */
+
+

This is the part that really defines what this script does, so this is the part you will probably spend the most time customizing.

+

The example code looks up a Ripple account (belonging to this writer) by its address. You can substitute your own address, if you want.

+

The console.log() function is a built-in tool in both Node.js and web browsers, which writes out to the console; this example includes lots of console output to make it easier to understand what the code is doing.

+

Keep in mind that the example code starts in the middle of a callback function (called when RippleAPI finishes connecting). That function calls RippleAPI's getAccountInfo method, and returns the results.

+

The results of that API method are another Promise, so the line }).then( info => { passes in another anonymous callback function to run when the second Promise's asynchronous work is done. Unlike the previous case, this callback function takes one argument, called info, which holds the -- actual -- return value from the getAccountInfo API method. The rest of this callback function just outputs that return value to the console.

+

Cleanup

+
}).then(() => {
+  return api.disconnect();
+}).then(()=> {
+  console.log('done and disconnected.');
+}).catch(console.error);
+
+

The remainder of the sample code is mostly more boilerplate code. The first line ends the previous callback function, then chains to another callback to run when it ends. That method disconnects cleanly from the Ripple Consensus Ledger, and has yet another callback which writes to the console when it finishes.

+

Finally, we get to the catch method of this entire long Promise chain. If any of the Promises or their callback functions encounters an error, the callback provided here will run. One thing worth noting: instead of defining a new anonymous callback function here, we can just pass in the standard console.error function, which writes whatever arguments it gets out to the console. If you so desired, you could define a smarter callback function here which might intelligently catch certain error types.

+

Waiting for Validation

+

One of the biggest challenges in using the Ripple Consensus Ledger (or any decentralized system) is knowing the final, immutable transaction results. Even if you follow the best practices you still have to wait for the consensus process to finally accept or reject your transaction. The following example code demonstrates how to wait for the final outcome of a transaction:

+
'use strict';
+/* import RippleAPI and support libraies*/
+const {RippleAPI} = require('ripple-lib');
+const assert = require('assert');
+
+/* Credentials of the account placing the offer */
+const my_addr = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn';
+const my_secret = 's████████████████████████████';
+
+/* Define the order to place here */
+const my_order = {
+  'direction': 'buy',
+  'quantity': {
+    'currency': '',
+    'counterparty': '',
+    'value': ''
+  },
+  'totalPrice': {
+    'currency': '',
+    'counterparty': '',
+    'value': ''
+  }
+};
+
+/* milliseconds to wait between ledger checks*/
+const INTERVAL = 1000;
+/* Instantiate RippleAPI */
+const api = new RippleAPI({server: 'wss://s1.ripple.com'});
+/* number of ledgers to check for valid transaction before fail */
+const ledgerOffset = 5;
+const my_instructions = {maxLedgerVersionOffset: ledgerOffset};
+
+
+/* function to verify a transaction is on the RCL */
+function verifyTransaction(hash, options) {
+  console.log('Verifing Transaction');
+  return api.getTransaction(hash, options).then(data => {
+    console.log('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 on current 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 result;// TODO: Fix this. It's currently undefined.
+  });
+}
+
+
+/* function to prepare, sign, and submit a transaction to the RCL
+success verifies the transaction is being considered for the next ledger.
+Still requires vlaidation */
+function submitTransaction(lastClosedLedgerVersion, prepared, secret) {
+  const signedData = api.sign(prepared.txJSON, secret);
+  return api.submit(signedData.signedTransaction).then(data => {
+    console.log('Result: ', data.resultCode);
+    console.log('Message: ', data.resultMessage);
+    /* if transaction was not successfully submitted throw error */
+    assert.strictEqual(data.resultCode, 'tesSUCCESS');
+    /* if successfully submitted fire off 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(my_addr, my_order, my_instructions);
+}).then(prepared => {
+  console.log('Order Prepared');
+  return api.getLedger().then(ledger => {
+    console.log('Current Ledger', ledger.ledgerVersion);
+    return submitTransaction(ledger.ledgerVersion, prepared, my_secret);
+  });
+}).then(() => {
+  api.disconnect().then(() => {
+    console.log('api disconnected');
+    process.exit();
+  });
+}).catch(console.error);
+
+