Compare commits

...

157 Commits

Author SHA1 Message Date
Geert Weening
9c9be3e6e4 Bump version to 0.13.0-rc5 2015-08-20 13:46:11 -07:00
Chris Clark
062561686e Update release notes 2015-08-20 11:49:04 -07:00
Geert Weening
761682c206 Merge branch 'develop' into release 2015-08-20 10:18:22 -07:00
Chris Clark
39c48d631c Merge pull request #513 from darkdarkdragon/develop-parse-trustline-fix
fix parseTrustline for case when no QualityIn or QualityOut are set i…
2015-08-19 11:20:32 -07:00
Ivan Tivonenko
a55d26a726 fix parseTrustline for case when no QualityIn or QualityOut are set in transaction 2015-08-19 21:17:50 +03:00
Chris Clark
597ae157b3 Merge pull request #515 from darkdarkdragon/develop-isvalidaddress-fix
in isValidAddress check that address starting with 'r'
2015-08-19 10:56:40 -07:00
wltsmrz
c1c7458914 Merge pull request #514 from sublimator/eslint-1.2.0
Update eslint to 1.2.0
2015-08-19 16:28:14 +07:00
Ivan Tivonenko
0e97f269ab in isValidAddress check that address starting with 'r'
because UInt160 considers valid hex values
2015-08-19 05:26:08 +03:00
Nicholas Dudfield
bbe4cd63a1 Update eslint to 1.2.0 2015-08-19 08:41:35 +07:00
Chris Clark
2515d17a85 Merge pull request #509 from clark800/generate-address
Rename generateWallet to generateAddress
2015-08-18 18:11:22 -07:00
Chris Clark
d8e95a3c3b Rename generateWallet to generateAddress 2015-08-18 17:44:10 -07:00
Chris Clark
98f6bed8c9 Merge pull request #511 from darkdarkdragon/develop-filter-fix
fix transactionFilter
2015-08-18 17:21:17 -07:00
Ivan Tivonenko
bd000c2662 fix transactionFilter 2015-08-19 03:15:10 +03:00
Chris Clark
a46141111a Merge pull request #512 from clark800/validate-address
Add isValidAddress
2015-08-18 14:48:44 -07:00
Chris Clark
f57c89c6e9 Add isValidAddress 2015-08-18 14:46:11 -07:00
Chris Clark
6d4cac948d Merge pull request #504 from clark800/get-ledger
Add getLedger method, remove getLedgerHeader method
2015-08-18 10:01:22 -07:00
Chris Clark
1f54b3a0cf Rename ledger "account" to "state" 2015-08-17 18:41:22 -07:00
Chris Clark
2f8655dc23 Add getLedger method, remove getLedgerHeader method 2015-08-17 18:39:34 -07:00
sublimator
d624923cd8 Merge pull request #510 from clark800/underscore-fixes
Fix snake case method calls
2015-08-18 07:33:56 +07:00
Chris Clark
2180c076dd Fix snake case method calls 2015-08-17 17:28:16 -07:00
Chris Clark
0dbdf0a21a Merge pull request #503 from darkdarkdragon/develop-RLJS-444-01
annotate api functions with types
2015-08-17 14:34:12 -07:00
Ivan Tivonenko
5cb63a258c annotate api functions with types 2015-08-17 20:51:03 +03:00
Chris Clark
39ac6caaef Merge pull request #506 from sublimator/uint-scraggles
Remove UInt#from_bn
2015-08-17 10:04:18 -07:00
Chris Clark
de4ef8b2b4 Merge pull request #505 from sublimator/reserve
[WIP] Fix reserve calculation
2015-08-17 10:01:10 -07:00
Nicholas Dudfield
99cba09a4a Remove UInt#from_bn 2015-08-16 21:31:46 +07:00
Nicholas Dudfield
300967f0f3 [WIP] Fix lint issues 2015-08-15 18:15:36 +07:00
Nicholas Dudfield
52879febb9 [WIP] Fix reserve calculation 2015-08-15 18:11:43 +07:00
Chris Clark
f077a563c4 Merge pull request #498 from clark800/compute-ledger-hash
Add computeLedgerHash method
2015-08-14 14:43:57 -07:00
Chris Clark
92fbc61f47 Add computeLedgerHash method 2015-08-14 14:25:56 -07:00
Chris Clark
5ac1bcc414 Merge pull request #502 from sublimator/hash.js
Remove sjcl-extended/ripple-wallet-generator. Use hash.js & sjcl-codec.
2015-08-14 11:14:33 -07:00
Nicholas Dudfield
5837aa23ea Remove sjcl-extended/ripple-wallet-generator. Use hash.js & sjcl-codec. 2015-08-14 09:31:49 +07:00
Chris Clark
8c431b4ec3 Merge pull request #499 from clark800/validated-ledger
Get validated ledger in getSettings and getAccountInfo
2015-08-13 13:20:55 -07:00
sublimator
25086a7944 Merge pull request #501 from sublimator/bn.js
Replace sjcl.bn with bn.js
2015-08-14 01:38:13 +07:00
Nicholas Dudfield
b0889b4afe Replace sjcl.bn with bn.js 2015-08-14 01:33:59 +07:00
wltsmrz
ed971bc41c Merge pull request #500 from clark800/get-ledger-header
Add getLedgerHeader method
2015-08-13 07:57:45 +07:00
Chris Clark
728595dc96 Add getLedgerHeader method 2015-08-12 17:37:13 -07:00
Chris Clark
7fc6adb776 Get validated ledger in getSettings and getAccountInfo 2015-08-12 15:58:47 -07:00
Chris Clark
002102ce62 Merge pull request #494 from clark800/streaming-pathfinding
Use streaming pathfinding: rippled path_find instead of ripple_path_find
2015-08-12 13:14:13 -07:00
Chris Clark
bf9da80d46 Fix lint errors in remote.js and pathfind.js 2015-08-12 12:34:12 -07:00
Chris Clark
dda9994869 Use streaming pathfinding: rippled path_find instead of ripple_path_find 2015-08-12 12:34:08 -07:00
sublimator
4c76ad159e Merge pull request #495 from sublimator/ripple-keypairs
Use ripple-keypairs and ripple-address-codec
2015-08-12 13:01:56 +07:00
Nicholas Dudfield
f76a8daca8 Update babel, eslint & co 2015-08-12 12:54:33 +07:00
Nicholas Dudfield
3263629ebe Use ripple-keypairs and ripple-address-codec 2015-08-12 12:54:30 +07:00
Chris Clark
fcbe7d3c98 Merge pull request #493 from clark800/max-amount-2
Rename source.amount to source.maxAmount
2015-08-10 14:29:47 -07:00
Chris Clark
a6662ccdff Rename source.amount to source.maxAmount 2015-08-10 14:15:30 -07:00
Chris Clark
854fe85151 Check for duplicate schema titles 2015-08-10 11:35:41 -07:00
Geert Weening
2b2fdf1b11 Bump version to 0.13.0-rc4 2015-08-07 15:32:21 -07:00
Geert Weening
cbe44d6a96 Update release notes 2015-08-07 15:32:21 -07:00
Geert Weening
420346faea Bump version to 0.13.0-rc3 2015-08-07 15:32:20 -07:00
Geert Weening
6220162852 Update release notes 2015-08-07 15:32:20 -07:00
Geert Weening
37198bde66 Bump version to 0.13.0-rc4 2015-08-07 15:31:56 -07:00
Geert Weening
281c056f6c Update release notes 2015-08-07 15:31:24 -07:00
Geert Weening
49a513cd07 Merge branch 'release' into develop 2015-08-07 14:47:26 -07:00
Alan Cohen
7a95aabbf4 Merge pull request #491 from lumberj/empty-paths
Don't set empty paths
2015-08-07 10:35:41 -07:00
Alan Cohen
83874ec096 Don't set empty paths
"The Paths field must not be an empty array, nor an array whose members are all
empty arrays."
https://ripple.com/build/transactions/#paths

Fix test file lint errors
2015-08-07 10:26:45 -07:00
Chris Clark
9270d0a33d Merge pull request #490 from clark800/test-dist
Clean up package.json
2015-08-06 15:30:09 -07:00
Chris Clark
30295efdb4 Clean up package.json 2015-08-06 15:09:49 -07:00
Chris Clark
f1342c1456 Merge pull request #489 from clark800/test-compiled
Test compiled code in dist/npm on CI server
2015-08-06 15:02:56 -07:00
Chris Clark
194b73c293 Test compiled code in dist/npm on CI server 2015-08-06 14:59:48 -07:00
Chris Clark
89e5f79bbb Merge pull request #487 from clark800/orderbook-changes
Switch to direction/quantity/totalPrice for orderbookChanges
2015-08-06 14:53:18 -07:00
Chris Clark
82d7ce7ac2 Switch to direction/quantity/totalPrice for orderbookChanges 2015-08-06 12:27:03 -07:00
Geert Weening
0cc4c704f8 Merge pull request #482 from clark800/deprecate
Deprecate core
2015-08-06 11:35:12 -07:00
Chris Clark
dde762a1d6 Merge pull request #483 from clark800/fix-prepare-trustline
Fix prepareTrustline with no quality setting
2015-08-06 10:40:34 -07:00
Chris Clark
1c86e246c7 Merge pull request #484 from darkdarkdragon/develop-schema-fix
small adjustment.json schema fix
2015-08-05 16:31:27 -07:00
Ivan Tivonenko
2d173c8e69 small adjustment.json schema fix 2015-08-06 02:25:59 +03:00
Chris Clark
600fd34d30 Fix prepareTrustline with no quality setting 2015-08-05 15:42:48 -07:00
Geert Weening
4cb9cf801c Bump version to 0.13.0-rc3 2015-08-04 17:20:25 -07:00
Geert Weening
50fb8789b4 Update release notes 2015-08-04 17:06:57 -07:00
Chris Clark
fb8dc44ec1 Deprecate core 2015-08-04 16:57:39 -07:00
Geert Weening
0781caa8bc Merge pull request #481 from clark800/pathfind-fix
Add test case for createPathFind and convert snake case function calls to camel case
2015-08-04 16:57:18 -07:00
Chris Clark
2cdb23f0dd Fix lint errors in pathfind.js 2015-08-04 15:51:52 -07:00
Chris Clark
8e536c00b9 Add test case for createPathFind and convert snake case function calls to camel case 2015-08-04 15:51:18 -07:00
Geert Weening
8ff154cc2d Merge pull request #480 from clark800/promise-docs
Update RippleAPI documentation for promises
2015-08-04 15:29:08 -07:00
Chris Clark
daaae6e01e Update RippleAPI documentation for promises 2015-08-04 14:53:46 -07:00
Chris Clark
a64a4e697a Merge pull request #478 from clark800/promises
Convert API to promises
2015-08-04 11:32:52 -07:00
wltsmrz
3f51d8cc12 Merge pull request #479 from wltsmrz/fix-validated-ledgers-single-ledger
Fix RangeSet for validated_ledger as single ledger
2015-08-05 00:48:48 +07:00
wltsmrz
9f9e76f8b9 Fix RangeSet for validated_ledger as single ledger 2015-08-04 10:45:29 -07:00
sublimator
5b51db158d Merge pull request #476 from clark800/badge
Replace Travis badge with CircleCI badge
2015-08-04 07:44:59 +07:00
Chris Clark
a4d1509448 Replace Travis badge with CircleCI badge 2015-08-03 17:39:01 -07:00
Chris Clark
bbd51a03b6 Convert API to promises 2015-08-03 17:22:17 -07:00
sublimator
b55b82b2fd Merge pull request #474 from shekenahglory/develop
add 'DeliveredAmount' as optional metadata field
2015-08-01 06:30:36 +07:00
Matthew Fettig
fdb0f101bd add 'DeliveredAmount' as optional metadata field 2015-07-31 16:21:18 -07:00
Chris Clark
0afca5633d Merge pull request #473 from clark800/remove-slippage
Remove slippage parameter
2015-07-31 11:06:32 -07:00
Chris Clark
7c0d9a7172 Remove slippage parameter 2015-07-31 10:43:39 -07:00
sublimator
f6b7e27c67 Merge pull request #472 from clark800/offline
Add unit test for offline prepare and sign
2015-07-31 08:47:29 +07:00
Chris Clark
b8624bc55f Add unit test for offline prepare and sign 2015-07-30 18:19:52 -07:00
sublimator
2eec30756d Merge pull request #471 from clark800/no-xrp-paths
Don't set paths for XRP to XRP payment
2015-07-31 06:46:12 +07:00
sublimator
6b44ce8973 Merge pull request #458 from clark800/fix-pathfind
Fix counterparty logic in pathfind parsing
2015-07-31 06:26:58 +07:00
Chris Clark
ed0b501716 Don't set paths for XRP to XRP payment 2015-07-30 13:43:44 -07:00
Geert Weening
0fd391af72 bump version to 0.13.0.rc2 2015-07-30 12:18:48 -07:00
Chris Clark
fe9c1ada88 Fix counterparty logic in pathfind parsing 2015-07-30 11:55:52 -07:00
Alan Cohen
4c1f4ef58c Merge pull request #470 from clark800/remove-asyncify
Remove simple-asyncify dependency
2015-07-30 11:47:03 -07:00
Chris Clark
10afc770ff Remove simple-asyncify dependency 2015-07-30 11:38:46 -07:00
Alan Cohen
8543e60f86 Merge pull request #469 from lumberj/update-flow
Update Flow to 0.14
2015-07-30 11:09:57 -07:00
Alan Cohen
116d7e0f29 Update Flow to 0.14
Fixes issue with fs.readFileSync return type
See: https://github.com/facebook/flow/issues/416
https://github.com/facebook/flow/compare/v0.13.1...v0.14.0
2015-07-30 10:06:53 -07:00
Chris Clark
68adaec55b Merge pull request #467 from darkdarkdragon/develop-RLJS-440
change snake_case to camelCase in responses from api.submit and api.g…
2015-07-29 17:31:36 -07:00
Ivan Tivonenko
03640efef5 change snake_case to camelCase in responses from api.submit and api.getServerInfo and add schema for it 2015-07-30 03:10:10 +03:00
Alan Cohen
c6f450842e Merge pull request #466 from lumberj/continue-flowing
Typecheck remaining files in src/api
2015-07-29 16:03:15 -07:00
Alan Cohen
e583eb4592 Typecheck remaining files in src/api 2015-07-29 15:59:07 -07:00
Chris Clark
7fffbe0c64 Merge pull request #465 from darkdarkdragon/develop-RLJS-370-12
more unit tests coverage
2015-07-29 11:22:06 -07:00
Ivan Tivonenko
63e3b71eb5 cover api/common/errors.js with tests 2015-07-29 21:15:28 +03:00
Ivan Tivonenko
823ef738fe cover api/common/utils.js with tests 2015-07-29 17:40:59 +03:00
Ivan Tivonenko
0977ef0ec2 cover api/common/validate.js with tests 2015-07-29 17:15:34 +03:00
Ivan Tivonenko
cecf3f3d22 cover api/ledger/parse/utils.js with tests 2015-07-29 16:42:23 +03:00
Ivan Tivonenko
472fbce23a cover api/ledger/parse/trustline.js with tests 2015-07-29 15:46:15 +03:00
sublimator
e44aea1767 Merge pull request #464 from wltsmrz/add-delivermin
Add DeliverMin setter
2015-07-29 14:52:56 +07:00
wltsmrz
d682d90d86 Add DeliverMin setter 2015-07-29 00:25:27 -07:00
Chris Clark
141aa17dfc Merge pull request #463 from darkdarkdragon/develop-RLJS-370-11
cover api/common/schema-validator.js with tests
2015-07-28 17:40:48 -07:00
Ivan Tivonenko
0b09e53479 cover api/common/schema-validator.js with tests 2015-07-29 03:22:17 +03:00
Chris Clark
528d8bf25d Merge pull request #457 from darkdarkdragon/develop-sign-with-regular-key
allow to sign transaction in api.sign using regular key
2015-07-28 15:53:37 -07:00
Ivan Tivonenko
03a2109e24 allow to sign transaction in api.sign using regular key
make Seed.parse_json try different input types instead
of stopping on first failing
2015-07-29 01:49:25 +03:00
Chris Clark
b38b9bced6 Merge pull request #462 from darkdarkdragon/develop-RLJS-370-10
cover api/ledger/parse/settings.js with tests
2015-07-28 14:05:04 -07:00
Ivan Tivonenko
ea063d0c95 cover api/ledger/parse/settings.js with tests 2015-07-28 22:21:25 +03:00
Chris Clark
7f93929014 Merge pull request #461 from lumberj/update-parser
Update ripple-lib-transactionparser to 0.4
2015-07-28 11:23:06 -07:00
Alan Cohen
4cd10ecb87 Update ripple-lib-transactionparser to 0.4 2015-07-28 09:55:01 -07:00
sublimator
6ef30debd2 Merge pull request #459 from wltsmrz/fix-transaction-constructor
Fix transaction construction for SetRegularKey transactions
2015-07-28 14:50:58 +07:00
wltsmrz
4766bace4e Fix transaction construction for SetRegularKey transactions 2015-07-28 00:47:30 -07:00
sublimator
261500a3a4 Merge pull request #460 from wltsmrz/fix-transaction-sequencing
Fix transaction sequencing
2015-07-28 14:37:32 +07:00
wltsmrz
fae22b7023 Fix transaction sequencing 2015-07-27 23:17:05 -07:00
Chris Clark
4568b39997 Merge pull request #450 from clark800/api-names
Rename API parameters
2015-07-27 17:06:14 -07:00
Chris Clark
4a218cacfa Merge pull request #449 from clark800/numbers
Express trustline quality and account transferRate as floats
2015-07-27 17:02:35 -07:00
Chris Clark
34a4dd3077 Express transferRate as a float 2015-07-27 16:59:58 -07:00
Chris Clark
a383bd7e52 Express trustline quality as a float 2015-07-27 16:56:46 -07:00
Chris Clark
e76e693bdb Merge pull request #452 from darkdarkdragon/develop-RLJS-370-9-1
Increase tests coverage even more
2015-07-27 16:35:59 -07:00
Ivan Tivonenko
2c52e4aa69 more unit tests coverage 2015-07-28 02:24:27 +03:00
Ivan Tivonenko
13dee36e93 propagate message from remote error to RippledNetworkError 2015-07-28 02:17:40 +03:00
Ivan Tivonenko
6e180439d1 cover api/ledger/transaction.js with unit tests 2015-07-28 02:17:38 +03:00
Ivan Tivonenko
e8d0c1ae95 make api.getTransaction return NotFound error in case of transaction not found
instead of core.RippleError
2015-07-28 02:16:30 +03:00
Ivan Tivonenko
068bda0c95 cover api/ledger/transactions.js with unit tests 2015-07-28 02:16:28 +03:00
Ivan Tivonenko
ab694381d5 fix some api functions to work with parsed version of transaction 2015-07-28 02:13:54 +03:00
Chris Clark
dc2a6c75cf Merge pull request #456 from clark800/docs
Update api.html doc
2015-07-27 14:57:13 -07:00
Chris Clark
98dbba8f27 Add api.html doc 2015-07-27 14:54:15 -07:00
Chris Clark
9a1b80d77a Merge pull request #451 from clark800/sample
Add sample API usage for getting balances and making a payment
2015-07-27 14:46:59 -07:00
Chris Clark
a655be30d6 Merge pull request #455 from clark800/doc
Add api.html
2015-07-27 14:46:50 -07:00
Chris Clark
e5aabc3072 Add api.html 2015-07-27 14:42:29 -07:00
Chris Clark
2cd32d58ad Merge pull request #454 from clark800/response-schemas
Add response schemas and small fixes
2015-07-27 14:22:55 -07:00
Chris Clark
0c02b92717 Add response schemas and small fixes 2015-07-27 14:20:09 -07:00
Geert Weening
c58a077a2f Merge branch 'release' into develop 2015-07-24 15:39:50 -07:00
Geert Weening
6e7dc9d7d3 Merge branch 'master' into release
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-07-24 15:39:00 -07:00
Geert Weening
572c945274 Bump version to 0.12.6 2015-07-24 15:32:07 -07:00
Geert Weening
c605efab61 Update release notes 2015-07-24 15:31:59 -07:00
wltsmrz
2695f4302a Merge pull request #436 from wltsmrz/fix-orderbook-notification
Fix orderbook notification while disconnected
2015-07-25 06:25:31 +08:00
Chris Clark
a17011243e Fix webpack require failure due to "./" notation 2015-07-24 15:24:40 -07:00
Geert Weening
9a533ab807 Merge branch 'develop' into release 2015-07-24 15:22:35 -07:00
wltsmrz
a037952493 Fix orderbook notification while disconnected 2015-07-24 15:22:21 -07:00
Geert Weening
dc96795a02 Merge pull request #453 from clark800/webpack-fix
Fix webpack require failure due to "./" notation
2015-07-24 15:03:44 -07:00
Chris Clark
8d9746d7b1 Fix webpack require failure due to "./" notation 2015-07-24 14:59:25 -07:00
Chris Clark
00342c4239 Merge pull request #446 from darkdarkdragon/develop-RLJS-370-8
Increase tests coverage
2015-07-23 16:47:42 -07:00
Chris Clark
e48df2c1fd Add sample API usage for getting balances and making a payment 2015-07-23 15:51:15 -07:00
Ivan Tivonenko
6ade0f6554 cover api/ledger/pathfind.js with tests 2015-07-24 01:34:54 +03:00
Ivan Tivonenko
a88157bb92 change pathfind scheme so destination can be specified without counterparty 2015-07-24 01:34:51 +03:00
Ivan Tivonenko
00f318284f cover api/transaction/utils.js with tests 2015-07-24 01:34:49 +03:00
Chris Clark
2e12dc6d53 Merge pull request #445 from darkdarkdragon/develop-RLJS-370-7
fix settings.json schema to allow empty stings to
2015-07-23 15:27:50 -07:00
Chris Clark
34435d4d05 Rename API parameters 2015-07-23 14:24:11 -07:00
Ivan Tivonenko
a99452b773 fix settings.json schema to allow null to
'walletSize', 'transferRate', 'emailHash' and
'walletLocator' fields so they
can be cleared

cover api/transaction/settings.js with tests
2015-07-23 23:29:00 +03:00
Geert Weening
a05cb39ab0 Merge pull request #447 from geertweening/develop
Remove `src/js`
2015-07-23 11:12:21 -07:00
Geert Weening
0c69f7f10e Remove src/js 2015-07-23 11:06:12 -07:00
179 changed files with 5567 additions and 2366 deletions

View File

@@ -1,3 +1,17 @@
##0.13.0 (release candidate)
+ [Deprecate core and remove snake case method copying](https://github.com/ripple/ripple-lib/commit/fb8dc44ec1d49bb05cd0cdbe6dd4ab211195868a)
+ Add new RippleAPI interface
- [RippleAPI README and samples](https://github.com/ripple/ripple-lib/tree/develop/docs/samples)
- [Method documentation](https://rawgit.com/ripple/ripple-lib/develop/docs/api.html)
+ [Fix RangeSet for validated_ledger as single ledger](https://github.com/ripple/ripple-lib/commit/9f9e76f8b933201651af59307135f67cfa7d60e8)
+ [Fix bug where the paths would be set with an empty array](https://github.com/ripple/ripple-lib/commit/83874ec0962da311b76f2385623e51c68bc39035)
+ [Fix reserve calculation](https://github.com/ripple/ripple-lib/commit/52879febb92d876f01f2e4d70871baa07af631fb)
##0.12.6
+ [Fix webpack require failure due to "./" notation](https://github.com/ripple/ripple-lib/commit/8d9746d7b10be203ee613df523c2522012ff1baf)
##0.12.15
+ [Add offer autobridging](https://github.com/ripple/ripple-lib/commit/c7bbce83719c1e8c6a4fae5ca850e7515db1a4a5)

View File

@@ -2,7 +2,7 @@
A JavaScript API for interacting with Ripple in Node.js and the browser
[![Build Status](https://travis-ci.org/ripple/ripple-lib.svg?branch=develop)](https://travis-ci.org/ripple/ripple-lib) [![Coverage Status](https://coveralls.io/repos/ripple/ripple-lib/badge.png?branch=develop)](https://coveralls.io/r/ripple/ripple-lib?branch=develop)
[![Circle CI](https://circleci.com/gh/ripple/ripple-lib/tree/develop.svg?style=svg)](https://circleci.com/gh/ripple/ripple-lib/tree/develop) [![Coverage Status](https://coveralls.io/repos/ripple/ripple-lib/badge.png?branch=develop)](https://coveralls.io/r/ripple/ripple-lib?branch=develop)
[![NPM](https://nodei.co/npm/ripple-lib.png)](https://www.npmjs.org/package/ripple-lib)

View File

@@ -5,19 +5,28 @@ TOTAL_NODES="$2"
typecheck() {
npm install -g flow-bin
flow --version
npm run typecheck
}
lint() {
echo "eslint $(node_modules/.bin/eslint --version)"
npm list babel-eslint | grep babel-eslint
REPO_URL="https://raw.githubusercontent.com/ripple/javascript-style-guide"
curl "$REPO_URL/es6/eslintrc" > ./eslintrc
echo "plugins: [flowtype]" >> ./eslintrc
node_modules/.bin/eslint --reset -c ./eslintrc $(git --no-pager diff --name-only -M100% --diff-filter=AM --relative $(git merge-base FETCH_HEAD origin/HEAD) FETCH_HEAD | grep "\.js$")
echo "parser: babel-eslint" >> ./eslintrc
node_modules/.bin/eslint -c ./eslintrc $(git --no-pager diff --name-only -M100% --diff-filter=AM --relative $(git merge-base FETCH_HEAD origin/HEAD) FETCH_HEAD | grep "\.js$")
}
unittest() {
# test "src"
npm test --coverage
npm run coveralls
# test compiled version in "dist/npm"
ln -nfs ../../dist/npm/core test/node_modules/ripple-lib
ln -nfs ../../dist/npm test/node_modules/ripple-api
npm test
}
oneNode() {

358
docs/api.html Normal file
View File

@@ -0,0 +1,358 @@
<html>
<head>
<style>
a {
text-decoration: none;
}
.method {
margin-bottom: 10px;
}
.details {
font-family: "Courier New", monospace;
white-space: pre;
display: none;
}
</style>
<script type="text/javascript">
function toggle(element) {
var results = element.parentElement.getElementsByClassName('details');
if (results.length > 0) {
var style = results[0].style;
style.display = (style.display === 'block') ? 'none' : 'block';
}
}
</script>
</head>
<body>
<h2><a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/index.js">RippleAPI</a></h2>
<div class="method">
connect()
</div>
<div class="method">
disconnect()
</div>
<div class="method">
isConnected()
</div>
<div class="method">
getServerInfo()
</div>
<div class="method">
getFee()
</div>
<div class="method">
getLedgerVersion()
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-transaction.json">getTransaction</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/hash256.json">identifier</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/transaction-options.json">options</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
identifier: txhash
options: {minLedgerVersion: int, maxLedgerVersion: int}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-transactions.json">getTransactions</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/transactions-options.json">options</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
options: {
start: txhash,
limit: int,
minLedgerVersion: int,
maxLedgerVersion: int,
earliestFirst: bool,
excludeFailures: bool,
initiated: bool,
counterparty: address,
types: [string],
binary: bool
}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-trustlines.json">getTrustlines</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/trustlines-options.json">options</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
options: {counterparty: address, currency: string, limit: int, ledgerVersion: int}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-balances.json">getBalances</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/trustlines-options.json">options</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
options: {counterparty: address, currency: string, limit: int, ledgerVersion: int}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-paths.json">getPaths</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/pathfind.json">pathfind</a>
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
pathfind: {
source: {
address: address,
currencies: [{
currency: string,
counterparty: address
}]
},
destination: adjustment
}
adjustment = {address: address, amount: amount, tag?: int}
amount = {currency: string, counterparty: address, value: floatstr}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-orders.json">getOrders</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/orders-options.json">options</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
options: {limit: int, ledverVersion: int}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-orderbook.json">getOrderbook</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/orderbook.json">orderbook</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/orders-options.json">options</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
orderbook: {
base: {
currency,
counterparty
},
counter: {
currency,
counterparty
}
}
options: {limit: int, ledverVersion: int}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-settings.json">getSettings</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/settings-options.json">options</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
options: {ledgerVersion: int}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/get-account-info.json">getAccountInfo</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/settings-options.json">options</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
options: {ledgerVersion: int}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/tx.json">preparePayment</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/payment.json">payment</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/instructions.json">instructions</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
payment: {
source: adjustment,
destination: adjustment,
paths?: string,
slippage?: strfloat,
memos?: [{
type: string,
format: string,
data: string
}],
invoiceID?: hash256,
allowPartialPayment?: bool,
noDirectRipple?: bool,
limitQuality?: bool
}
adjustment = {address: address, amount: amount, tag?: int}
amount = {currency: string, counterparty: address, value: floatstr}
instructions: {
sequence: int,
fee: floatstr,
maxFee: floatstr,
maxLedgerVersion: int,
maxLedgerVersionOffset: int
}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/tx.json">prepareTrustline</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/trustline.json">trustline</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/instructions.json">instructions</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
trustline: {
currency: string,
counterparty: address,
limit: strfloat,
qualityIn?: float,
qualityOut?: float,
allowRippling?: bool,
authorized?: bool,
frozen?: bool
}
instructions: {
sequence: int,
fee: floatstr,
maxFee: floatstr,
maxLedgerVersion: int,
maxLedgerVersionOffset: int
}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/tx.json">prepareOrder</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/order.json">order</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/instructions.json">instructions</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
order: {
direction: ("buy"|"sell"),
quantity: amount,
totalPrice: amount,
immediateOrCancel?: bool,
fillOrKill?: bool,
passive?: bool
}
amount = {currency: string, counterparty: address, value: floatstr}
instructions: {
sequence: int,
fee: floatstr,
maxFee: floatstr,
maxLedgerVersion: int,
maxLedgerVersionOffset: int
}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/tx.json">prepareOrderCancellation</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/sequence.json">sequence</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/instructions.json">instructions</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
sequence: int
instructions: {
sequence: int,
fee: floatstr,
maxFee: floatstr,
maxLedgerVersion: int,
maxLedgerVersionOffset: int
}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/tx.json">prepareSettings</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/address.json">account</a>,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/settings.json">settings</a>[,
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/instructions.json">instructions</a>]
)
<a href="#" onclick="javascript:toggle(this)">+</a>
<div class="details">
settings: {
passwordSpent: bool,
requireDestinationTag: bool,
requireAuthorization: bool,
disallowIncomingXRP: bool,
disableMasterKey: bool,
enableTransactionIDTracking: bool,
noFreeze: bool,
globalFreeze: bool,
defaultRipple: bool,
emailHash: hash128,
walletLocator: hash256,
walletSize: int,
messageKey: string,
domain: string,
transferRate: float,
signers: string,
regularKey: address
}
instructions: {
sequence: int,
fee: floatstr,
maxFee: floatstr,
maxLedgerVersion: int,
maxLedgerVersionOffset: int
}
</div>
</div>
<div class="method">
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/sign.json">sign</a>(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/tx.json">txJSON</a>,
secret)
</div>
<div class="method">
submit(
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/schemas/blob.json">txBlob</a>
)
</div>
<div class="method">
generateWallet()
</div>
<a href="https://github.com/ripple/ripple-lib/blob/develop/src/api/common/errors.js">errors</a>
</body>
</html>

3
docs/samples/README Normal file
View File

@@ -0,0 +1,3 @@
Usage:
babel-node balances.js
babel-node payment.js (requires setting address and secret in source file first)

12
docs/samples/balances.js Normal file
View File

@@ -0,0 +1,12 @@
'use strict';
const RippleAPI = require('../../src').RippleAPI; // require('ripple-lib')
const api = new RippleAPI({servers: ['wss://s1.ripple.com:443']});
const address = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
api.connect().then(() => {
api.getBalances(address).then(balances => {
console.log(JSON.stringify(balances, null, 2));
process.exit();
});
});

41
docs/samples/payment.js Normal file
View File

@@ -0,0 +1,41 @@
'use strict';
const RippleAPI = require('../../src').RippleAPI; // require('ripple-lib')
const address = 'INSERT ADDRESS HERE';
const secret = 'INSERT SECRET HERE';
const api = new RippleAPI({servers: ['wss://s1.ripple.com:443']});
const instructions = {maxLedgerVersionOffset: 5};
const payment = {
source: {
address: address,
amount: {
value: '0.01',
currency: 'XRP'
}
},
destination: {
address: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
amount: {
value: '0.01',
currency: 'XRP'
}
}
};
api.connect().then(() => {
console.log('Connected...');
api.preparePayment(address, payment, instructions).then(txJSON => {
console.log('Payment transaction prepared...');
const signedTransaction = api.sign(txJSON, secret).signedTransaction;
console.log('Payment transaction signed...');
api.submit(signedTransaction).then(response => {
console.log(response);
process.exit(0);
}).catch(error => {
console.log(error);
process.exit(1);
});
});
});

102
npm-shrinkwrap.json generated
View File

@@ -1,20 +1,20 @@
{
"name": "ripple-lib",
"version": "0.13.0-rc1",
"version": "0.13.0-rc5",
"npm-shrinkwrap-version": "5.4.0",
"node-version": "v0.12.6",
"node-version": "v0.12.7",
"dependencies": {
"async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
},
"babel-runtime": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.3.tgz",
"version": "5.8.20",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.20.tgz",
"dependencies": {
"core-js": {
"version": "0.9.18",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-0.9.18.tgz"
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.0.1.tgz"
}
}
},
@@ -22,10 +22,34 @@
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz"
},
"bn.js": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.1.1.tgz"
},
"es6-promisify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-2.0.0.tgz",
"dependencies": {
"es6-promise": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz"
}
}
},
"extend": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
},
"hash.js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"https-proxy-agent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz",
@@ -57,8 +81,8 @@
}
},
"is-my-json-valid": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.0.tgz",
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.1.tgz",
"dependencies": {
"generate-function": {
"version": "2.0.0",
@@ -85,16 +109,52 @@
}
},
"lodash": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.0.tgz"
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
},
"lru-cache": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz"
},
"ripple-address-codec": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-1.6.0.tgz",
"dependencies": {
"x-address-codec": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.6.0.tgz",
"dependencies": {
"base-x": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz"
}
}
}
}
},
"ripple-keypairs": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.8.0.tgz",
"dependencies": {
"brorand": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz"
},
"elliptic": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-5.1.0.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
}
}
},
"ripple-lib-transactionparser": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.3.2.tgz",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.5.0.tgz",
"dependencies": {
"bignumber.js": {
"version": "1.4.1",
@@ -102,23 +162,9 @@
}
}
},
"ripple-wallet-generator": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ripple-wallet-generator/-/ripple-wallet-generator-1.0.3.tgz"
},
"simple-asyncify": {
"sjcl-codec": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/simple-asyncify/-/simple-asyncify-0.1.0.tgz"
},
"sjcl-extended": {
"version": "1.0.3",
"resolved": "git://github.com/ripple/sjcl-extended.git#d8cf8b22e7d97193c54e1f65113e3edcf200ca17",
"dependencies": {
"sjcl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.3.tgz"
}
}
"resolved": "https://registry.npmjs.org/sjcl-codec/-/sjcl-codec-0.1.0.tgz"
},
"ws": {
"version": "0.7.2",

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.13.0-rc1",
"version": "0.13.0-rc5",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -18,28 +18,31 @@
"async": "~0.9.0",
"babel-runtime": "^5.5.4",
"bignumber.js": "^2.0.3",
"bn.js": "^3.1.1",
"es6-promisify": "^2.0.0",
"extend": "~1.2.1",
"hash.js": "^1.0.3",
"https-proxy-agent": "^1.0.0",
"is-my-json-valid": "^2.12.0",
"lodash": "^3.1.0",
"lru-cache": "~2.5.0",
"ripple-lib-transactionparser": "^0.3.2",
"ripple-wallet-generator": "^1.0.3",
"simple-asyncify": "^0.1.0",
"sjcl-extended": "ripple/sjcl-extended#1.0.3",
"ripple-address-codec": "^1.6.0",
"ripple-keypairs": "^0.8.0",
"ripple-lib-transactionparser": "^0.5.0",
"sjcl-codec": "0.1.0",
"ws": "~0.7.1"
},
"devDependencies": {
"assert-diff": "^1.0.1",
"babel": "^5.5.4",
"babel-core": "^5.5.4",
"babel-eslint": "^3.1.23",
"babel-loader": "^5.0.0",
"babel": "^5.8.21",
"babel-core": "^5.8.22",
"babel-eslint": "^4.0.5",
"babel-loader": "^5.3.2",
"coveralls": "~2.10.0",
"eslint": "^0.24.0",
"eslint": "^1.2.0",
"eslint-plugin-flowtype": "^1.0.0",
"eventemitter2": "^0.4.14",
"flow-bin": "^0.13.1",
"flow-bin": "^0.14",
"gulp": "~3.8.10",
"gulp-bump": "~0.1.13",
"gulp-rename": "~1.2.0",
@@ -53,12 +56,13 @@
"build": "gulp",
"clean": "rm -rf dist/npm && rm -rf build/flow",
"typecheck": "babel --optional runtime --blacklist flow -d build/flow/ src/ && flow check",
"compile": "babel --optional runtime -d dist/npm/ src/ && cp -r src/api/common/schemas/ dist/npm/api/common/schemas/",
"compile-with-source-maps": "babel --optional runtime -s -t -d dist/npm/ src/",
"compile": "babel -D --optional runtime -d dist/npm/ src/",
"watch": "babel -w -D --optional runtime -d dist/npm/ src/",
"compile-with-source-maps": "babel -D --optional runtime -s -t -d dist/npm/ src/",
"prepublish": "npm run clean && npm run compile",
"test": "istanbul test _mocha",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; echo 'plugins:\n - flowtype' >> eslintrc; fi; eslint --reset -c eslintrc src/",
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; echo 'parser: babel-eslint' >> eslintrc; fi; eslint -c eslintrc src/",
"perf": "./scripts/perf_test.sh"
},
"repository": {

View File

@@ -2,12 +2,13 @@
'use strict';
var fs = require('fs');
var Amount = require('../dist/npm').Amount;
var Ledger = require('../dist/npm').Ledger;
var ripple = require('../dist/npm')._DEPRECATED;
var Amount = ripple.Amount;
var Ledger = ripple.Ledger;
function parse_options(from, flags) {
var argv = from.slice(),
opts_ = {argv: argv};
var argv = from.slice();
var opts_ = {argv: argv};
flags.forEach(function(f) {
// Do we have the flag?

View File

@@ -33,7 +33,7 @@ const AccountFields = {
WalletSize: {name: 'walletSize', defaults: 0},
MessageKey: {name: 'messageKey'},
Domain: {name: 'domain', encoding: 'hex'},
TransferRate: {name: 'transferRate', defaults: 0},
TransferRate: {name: 'transferRate', defaults: 0, shift: 9},
Signers: {name: 'signers'}
};

View File

@@ -9,7 +9,12 @@ module.exports = {
dropsToXrp: utils.dropsToXrp,
xrpToDrops: utils.xrpToDrops,
toRippledAmount: utils.toRippledAmount,
wrapCatch: utils.wrapCatch,
generateAddress: utils.generateAddress,
composeAsync: utils.composeAsync,
convertExceptions: utils.convertExceptions
wrapCatch: utils.wrapCatch,
convertExceptions: utils.convertExceptions,
convertKeysFromSnakeCaseToCamelCase:
utils.convertKeysFromSnakeCaseToCamelCase,
promisify: utils.promisify,
isValidAddress: require('./schema-validator').isValidAddress
};

View File

@@ -1,22 +1,27 @@
/* @flow */
'use strict';
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const validator = require('is-my-json-valid');
const core = require('./utils').core;
const ValidationError = require('./errors').ValidationError;
let SCHEMAS = {};
function isValidAddress(address) {
return core.UInt160.is_valid(address);
function isValidAddress(address: string): boolean {
return typeof address === 'string' && address.length > 0 &&
address[0] === 'r' &&
core.UInt160.is_valid(address);
}
function isValidLedgerHash(ledgerHash) {
return core.UInt256.is_valid(ledgerHash);
}
function loadSchema(filepath) {
function loadSchema(filepath: string): {} {
try {
return JSON.parse(fs.readFileSync(filepath, 'utf8'));
} catch (e) {
@@ -31,6 +36,9 @@ function endsWith(str, suffix) {
function loadSchemas(dir) {
const filenames = fs.readdirSync(dir).filter(name => endsWith(name, '.json'));
const schemas = filenames.map(name => loadSchema(path.join(dir, name)));
const titles = _.map(schemas, schema => schema.title);
const duplicates = _.keys(_.pick(_.countBy(titles), count => count > 1));
assert(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates);
return _.indexBy(schemas, 'title');
}
@@ -43,7 +51,7 @@ function formatSchemaErrors(errors) {
return errors.map(formatSchemaError).join(', ');
}
function schemaValidate(schemaName, object) {
function schemaValidate(schemaName: string, object: any): void {
const formats = {address: isValidAddress,
ledgerHash: isValidLedgerHash};
const options = {schemas: SCHEMAS, formats: formats,
@@ -60,4 +68,9 @@ function schemaValidate(schemaName, object) {
}
SCHEMAS = loadSchemas(path.join(__dirname, './schemas'));
module.exports = schemaValidate;
module.exports = {
schemaValidate: schemaValidate,
isValidAddress: isValidAddress,
loadSchema: loadSchema,
SCHEMAS: SCHEMAS
};

View File

@@ -4,7 +4,16 @@
"type": "object",
"properties": {
"address": {"$ref": "address"},
"amount": {"$ref": "amount"},
"amount": {
"type": "object",
"properties": {
"currency": {"$ref": "currency"},
"counterparty": {"$ref": "address"},
"value": {"$ref": "value"}
},
"required": ["currency", "value"],
"additionalProperties": false
},
"tag": {
"description": "A string representing an unsigned 32-bit integer most commonly used to refer to a sender's hosted account at a Ripple gateway",
"$ref": "uint32"

View File

@@ -0,0 +1,44 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "balance",
"description": "Balance amount",
"type": "object",
"properties": {
"value": {
"description": "The balance on the trustline",
"$ref": "signedValue"
},
"currency": {
"description": "The three-character code or hex string used to denote currencies",
"$ref": "currency"
},
"counterparty": {
"description": "The Ripple account address of the currency's issuer or gateway",
"$ref": "address"
}
},
"additionalProperties": false,
"required": ["currency", "value"],
"oneOf": [
{
"properties": {
"currency": {
"not": {
"enum": ["XRP"]
}
}
},
"required": ["counterparty"]
},
{
"properties": {
"currency": {
"enum": ["XRP"]
}
},
"not": {
"required": ["counterparty"]
}
}
]
}

View File

@@ -0,0 +1,21 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getAccountInfo",
"type": "object",
"properties": {
"sequence": {"$ref": "sequence"},
"xrpBalance": {"$ref": "value"},
"ownerCount": {"type": "integer", "minimum": 0},
"previousInitiatedTransactionID": {"$ref": "hash256"},
"previousAffectingTransactionID": {"$ref": "hash256"},
"previousAffectingTransactionLedgerVersion": {"$ref": "ledgerVersion"}
},
"required": [
"sequence",
"xrpBalance",
"ownerCount",
"previousAffectingTransactionID",
"previousAffectingTransactionLedgerVersion"
],
"additionalProperties": false
}

View File

@@ -0,0 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getBalances",
"type": "array",
"items": {"$ref": "balance"}
}

View File

@@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getLedger",
"type": "object",
"properties": {
"accepted": {"type": "boolean"},
"closed": {"type": "boolean"},
"stateHash": {"$ref": "hash256"},
"closeTime": {"type": "integer", "minimum": 0},
"closeTimeResolution": {"type": "integer", "minimum": 1},
"closeFlags": {"type": "integer", "minimum": 0},
"ledgerHash": {"$ref": "hash256"},
"ledgerVersion": {"$ref": "ledgerVersion"},
"parentLedgerHash": {"$ref": "hash256"},
"parentCloseTime": {"type": "integer", "minimum": 0},
"totalDrops": {"$ref": "value"},
"transactionHash": {"$ref": "hash256"},
"transactions": {"type": "array", "items": {"type": "object"}},
"rawTransactions": {"type": "string"},
"transactionHashes": {"type": "array", "items": {"$ref": "hash256"}},
"rawState": {"type": "string"},
"stateHashes": {"type": "array", "items": {"$ref": "hash256"}}
},
"required": [
"accepted",
"closed",
"stateHash",
"closeTime",
"closeTimeResolution",
"closeFlags",
"ledgerHash",
"ledgerVersion",
"parentLedgerHash",
"parentCloseTime",
"totalDrops",
"transactionHash"
],
"additionalProperties": false
}

View File

@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getOrderbook",
"type": "object",
"properties": {
"bids": {"$ref": "orderbookOrders"},
"asks": {"$ref": "orderbookOrders"}
},
"required": ["bids", "asks"],
"additionalProperties": false
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getOrders",
"type": "array",
"items": {
"type": "object",
"properties": {
"specification": {"$ref": "order"},
"properties": {
"type": "object",
"properties": {
"maker": {"$ref": "address"},
"sequence": {"$ref": "sequence"},
"makerExchangeRate": {"$ref": "value"}
},
"required": ["maker", "sequence", "makerExchangeRate"],
"addtionalProperties": false
}
},
"required": ["specification", "properties"],
"additionalProperties": false
}
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getPaths",
"type": "array",
"items": {
"type": "object",
"properties": {
"source": {"$ref": "adjustment"},
"destination": {"$ref": "adjustment"},
"paths": {"type": "string"}
},
"required": ["source", "destination", "paths"],
"additionalProperties": false
}
}

View File

@@ -0,0 +1,49 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getServerInfo",
"type": "object",
"properties": {
"buildVersion": {"type": "string"},
"completeLedgers": {"type": "string", "pattern": "[0-9,-]+"},
"hostid": {"type": "string"},
"ioLatencyMs": {"type": "number"},
"load": {
"type": "object",
"properties": {
"job_types": {
"type": "array",
"items": {"type": "object"}
},
"threads": {"type": "number"}
}
},
"lastClose": {
"type": "object",
"properties": {
"convergeTimeS": {"type": "number"},
"proposers": {"type": "integer", "minimum": 0}
}
},
"loadFactor": {"type": "number"},
"peers": {"type": "integer", "minimum": 0},
"pubkeyNode": {"type": "string"},
"pubkeyValidator": {"type": "string"},
"serverState": {
"type": "string",
"enum": ["disconnected", "connected", "syncing", "tracking", "full", "validating", "proposing"]
},
"validatedLedger": {
"type": "object",
"properties": {
"age": {"type": "integer", "minimum": 0},
"baseFeeXrp": {"type": "number"},
"hash": {"$ref": "hash256"},
"reserveBaseXrp": {"type": "integer", "minimum": 0},
"reserveIncXrp": {"type": "integer", "minimum": 0},
"seq": {"type": "integer", "minimum": 0}
}
},
"validationQuorum": {"type": "number"}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getSettings",
"type": "object",
"properties": {
"passwordSpent": {"type": "boolean"},
"requireDestinationTag": {"type": "boolean"},
"requireAuthorization": {"type": "boolean"},
"disallowIncomingXRP": {"type": "boolean"},
"disableMasterKey": {"type": "boolean"},
"enableTransactionIDTracking": {"type": "boolean"},
"noFreeze": {"type": "boolean"},
"globalFreeze": {"type": "boolean"},
"defaultRipple": {"type": "boolean"},
"emailHash": {
"oneOf": [
{"type": "null"},
{"$ref": "hash128"}
]
},
"walletLocator": {
"oneOf": [
{"type": "null"},
{"$ref": "hash256"}
]
},
"walletSize": {"type": ["integer", "null"]},
"messageKey": {"type": "string"},
"domain": {"type": "string"},
"transferRate": {
"oneOf": [
{"type": "null"},
{"type": "number", "minimum": 1, "maximum": 4.294967295}
]
},
"signers": {"type": "string"},
"regularKey": {"$ref": "address"}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getTransaction",
"oneOf": [
{"$ref": "paymentTransaction"},
{"$ref": "orderTransaction"},
{"$ref": "orderCancellationTransaction"},
{"$ref": "trustlineTransaction"},
{"$ref": "settingsTransaction"}
]
}

View File

@@ -0,0 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getTransactions",
"type": "array",
"items": {"$ref": "getTransaction"}
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getTrustlines",
"type": "array",
"items": {
"properties": {
"specification": {"$ref": "trustline"},
"counterparty": {
"properties": {
"limit": {"$ref": "value"},
"ripplingDisabled": {"type": "boolean"},
"frozen": {"type": "boolean"},
"authorized": {"type": "boolean"}
},
"required": ["limit"],
"additionalProperties": false
},
"state": {
"properties": {
"balance": {"$ref": "value"}
},
"required": ["balance"],
"additionalProperties": false
}
},
"required": ["specification", "counterparty", "state"],
"additionalProperties": false
}
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "ledger-options",
"description": "Options for getLedger",
"type": "object",
"properties": {
"ledgerVersion": {"$ref": "ledgerVersion"},
"includeAllData": {"type": "boolean"},
"includeTransactions": {"type": "boolean"},
"includeState": {"type": "boolean"}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "maxAdjustment",
"type": "object",
"properties": {
"address": {"$ref": "address"},
"maxAmount": {
"type": "object",
"properties": {
"currency": {"$ref": "currency"},
"counterparty": {"$ref": "address"},
"value": {"$ref": "value"}
},
"required": ["currency", "value"],
"additionalProperties": false
},
"tag": {
"description": "A string representing an unsigned 32-bit integer most commonly used to refer to a sender's hosted account at a Ripple gateway",
"$ref": "uint32"
}
},
"required": ["address", "maxAmount"],
"additionalProperties": false
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "orderCancellationTransaction",
"type": "object",
"properties": {
"type": {"enum": ["orderCancellation"]},
"specification": {"$ref": "orderCancellation"},
"outcome": {"$ref": "outcome"},
"id": {"$ref": "hash256"},
"address": {"$ref": "address"},
"sequence": {"$ref": "sequence"}
},
"required": ["type", "id", "address", "sequence", "specification", "outcome"],
"additionalProperties": false
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "cancellation",
"title": "orderCancellation",
"type": "object",
"properties": {
"orderSequence": {"$ref": "sequence"}

View File

@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "orderChange",
"type": "object",
"properties": {
"direction": {
"type": "string",
"enum": ["buy", "sell"]
},
"quantity": {"$ref": "balance"},
"totalPrice": {"$ref": "balance"},
"sequence": {"$ref": "sequence"},
"status": {"enum": ["created", "open", "closed", "canceled"]}
},
"required": ["direction", "quantity", "totalPrice", "sequence", "status"],
"additionalProperties": false
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "orderTransaction",
"type": "object",
"properties": {
"type": {"enum": ["order"]},
"specification": {"$ref": "order"},
"outcome": {"$ref": "outcome"},
"id": {"$ref": "hash256"},
"address": {"$ref": "address"},
"sequence": {"$ref": "sequence"}
},
"required": ["type", "id", "address", "sequence", "specification", "outcome"],
"additionalProperties": false
}

View File

@@ -0,0 +1,32 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "orderbookOrders",
"type": "array",
"items": {
"type": "object",
"properties": {
"specification": {"$ref": "order"},
"properties": {
"type": "object",
"properties": {
"maker": {"$ref": "address"},
"sequence": {"$ref": "sequence"},
"makerExchangeRate": {"$ref": "value"}
},
"required": ["maker", "sequence", "makerExchangeRate"],
"addtionalProperties": false
},
"state": {
"type": "object",
"properties": {
"fundedAmount": {"$ref": "amount"},
"priceOfFundedAmount": {"$ref": "amount"}
},
"required": ["fundedAmount", "priceOfFundedAmount"],
"additionalProperties": false
}
},
"required": ["specification", "properties"],
"additionalProperties": false
}
}

View File

@@ -0,0 +1,31 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "outcome",
"type": "object",
"properties": {
"result": {"type": "string"},
"timestamp": {"type": "string"},
"fee": {"$ref": "value"},
"balanceChanges": {
"type": "object",
"description": "Key is the ripple address; value is an array of changes",
"additionalProperties": {
"type": "array",
"items": {"$ref": "balance"}
}
},
"orderbookChanges": {
"type": "object",
"description": "Key is the maker's ripple address; value is an array of changes",
"additionalProperties": {
"type": "array",
"items": {"$ref": "orderChange"}
}
},
"ledgerVersion": {"$ref": "ledgerVersion"},
"indexInLedger": {"type": "integer", "minimum": 0}
},
"required": ["result", "fee", "balanceChanges",
"orderbookChanges", "ledgerVersion", "indexInLedger"],
"additionalProperties": false
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "paymentTransaction",
"type": "object",
"properties": {
"type": {"enum": ["payment"]},
"specification": {"$ref": "payment"},
"outcome": {"$ref": "outcome"},
"id": {"$ref": "hash256"},
"address": {"$ref": "address"},
"sequence": {"$ref": "sequence"}
},
"required": ["type", "id", "address", "sequence", "specification", "outcome"],
"additionalProperties": false
}

View File

@@ -3,13 +3,9 @@
"title": "payment",
"type": "object",
"properties": {
"source": {"$ref": "adjustment"},
"source": {"$ref": "maxAdjustment"},
"destination": {"$ref": "adjustment"},
"paths": {"type": "string"},
"slippage": {
"description": "An optional cushion for the source_amount to increase the likelihood that the payment will succeed. The source_account will never be charged more than source_amount.value + source_slippage",
"$ref": "value"
},
"memos": {
"type": "array",
"items": {

View File

@@ -1,8 +1,8 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "quality",
"description": "Ratio for incoming/outgoing transit fees represented in billionths. (For example, a value of 500 million represents a 0.5:1 ratio.) As a special case, 0 is treated as a 1:1 ratio.",
"type": "integer",
"minimum": 0,
"maximum": 1000000000
"description": "Ratio for incoming/outgoing transit fees.",
"type": "number",
"minimum": 0.000000001,
"maximum": 4.294967295
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "settingsTransaction",
"type": "object",
"properties": {
"type": {"enum": ["settings"]},
"specification": {"$ref": "getSettings"},
"outcome": {"$ref": "outcome"},
"id": {"$ref": "hash256"},
"address": {"$ref": "address"},
"sequence": {"$ref": "sequence"}
},
"required": ["type", "id", "address", "sequence", "specification"],
"additionalProperties": false
}

View File

@@ -1,27 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "settings",
"type": "object",
"properties": {
"passwordSpent": {"type": "boolean"},
"requireDestinationTag": {"type": "boolean"},
"requireAuthorization": {"type": "boolean"},
"disallowIncomingXRP": {"type": "boolean"},
"disableMasterKey": {"type": "boolean"},
"enableTransactionIDTracking": {"type": "boolean"},
"noFreeze": {"type": "boolean"},
"globalFreeze": {"type": "boolean"},
"defaultRipple": {"type": "boolean"},
"emailHash": {"$ref": "hash128"},
"walletLocator": {"$ref": "hash256"},
"walletSize": {"type": "integer"},
"messageKey": {"type": "string"},
"domain": {"type": "string"},
"transferRate": {"type": "integer"},
"signers": {"type": "string"},
"regularKey": {"$ref": "address"}
},
"minProperties": 1,
"maxProperties": 1,
"additionalProperties": false
"allOf": [
{
"$ref": "getSettings"
},
{
"minProperties": 1,
"maxProperties": 1
}
]
}

View File

@@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "sign",
"type": "object",
"properties": {
"signedTransaction": {
"type": "string",
"pattern": "^[A-F0-9]+$"
},
"id": {"$ref": "hash256"}
},
"required": ["signedTransaction", "id"],
"additionalProperties": false
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "signedValue",
"description": "A string representation of a floating point number",
"type": "string",
"pattern": "^[-]?[0-9]*[.]?[0-9]+([eE][-+]?[0-9]+)?$"
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "submit",
"type": "object",
"properties": {
"success": {"type": "boolean"},
"engineResult": {"type": "string"},
"engineResultCode": {"type": "integer"},
"engineResultMessage": {"type": "string"},
"txBlob": {"type": "string"},
"txJson": {"type": "object"}
},
"required": ["success", "engineResult", "engineResultCode"],
"additionalProperties": false
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "trustlineTransaction",
"type": "object",
"properties": {
"type": {"enum": ["trustline"]},
"specification": {"$ref": "trustline"},
"outcome": {"$ref": "outcome"},
"id": {"$ref": "hash256"},
"address": {"$ref": "address"},
"sequence": {"$ref": "sequence"}
},
"required": ["type", "id", "address", "sequence", "specification", "outcome"],
"additionalProperties": false
}

View File

@@ -8,7 +8,7 @@
"limit": {"$ref": "value"},
"qualityIn": {"$ref": "quality"},
"qualityOut": {"$ref": "quality"},
"allowRippling": {"type": "boolean"},
"ripplingDisabled": {"type": "boolean"},
"authorized": {"type": "boolean"},
"frozen": {"type": "boolean"}
},

View File

@@ -1,17 +1,23 @@
/* @flow */
'use strict';
const _ = require('lodash');
const BigNumber = require('bignumber.js');
const core = require('../../core');
const errors = require('./errors');
const es6promisify = require('es6-promisify');
const keypairs = require('ripple-keypairs');
function dropsToXrp(drops) {
type Amount = {currency: string, issuer: string, value: string}
function dropsToXrp(drops: string): string {
return (new BigNumber(drops)).dividedBy(1000000.0).toString();
}
function xrpToDrops(xrp) {
function xrpToDrops(xrp: string): string {
return (new BigNumber(xrp)).times(1000000.0).floor().toString();
}
function toRippledAmount(amount) {
function toRippledAmount(amount: Amount): string|Amount {
if (amount.currency === 'XRP') {
return xrpToDrops(amount.value);
}
@@ -22,7 +28,14 @@ function toRippledAmount(amount) {
};
}
function wrapCatch(asyncFunction: () => void): () => void {
function generateAddress(options?: Object): Object {
const {accountID, seed} = keypairs.generateWallet(options);
return {secret: seed, address: accountID};
}
type AsyncFunction = (...x: any) => void
function wrapCatch(asyncFunction: AsyncFunction): AsyncFunction {
return function() {
try {
asyncFunction.apply(this, arguments);
@@ -33,7 +46,10 @@ function wrapCatch(asyncFunction: () => void): () => void {
};
}
function composeAsync(wrapper, callback) {
type Callback = (err: any, data: any) => void
type Wrapper = (data: any) => any
function composeAsync(wrapper: Wrapper, callback: Callback): Callback {
return function(error, data) {
if (error) {
callback(error);
@@ -50,7 +66,7 @@ function composeAsync(wrapper, callback) {
};
}
function convertExceptions(f) {
function convertExceptions<T>(f: () => T): () => T {
return function() {
try {
return f.apply(this, arguments);
@@ -60,12 +76,35 @@ function convertExceptions(f) {
};
}
const FINDSNAKE = /([a-zA-Z]_[a-zA-Z])/g;
function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
if (typeof obj === 'object') {
let newKey;
return _.reduce(obj, (result, value, key) => {
newKey = key;
if (FINDSNAKE.test(key)) {
newKey = key.replace(FINDSNAKE, r => r[0] + r[2].toUpperCase());
}
result[newKey] = convertKeysFromSnakeCaseToCamelCase(value);
return result;
}, {});
}
return obj;
}
function promisify(asyncFunction: AsyncFunction): Function {
return es6promisify(wrapCatch(asyncFunction));
}
module.exports = {
core,
dropsToXrp,
xrpToDrops,
toRippledAmount,
wrapCatch,
generateAddress,
composeAsync,
convertExceptions
wrapCatch,
convertExceptions,
convertKeysFromSnakeCaseToCamelCase,
promisify
};

View File

@@ -1,26 +1,38 @@
/* @flow */
'use strict';
const _ = require('lodash');
const core = require('./utils').core;
const ValidationError = require('./errors').ValidationError;
const schemaValidate = require('./schema-validator');
const schemaValidate = require('./schema-validator').schemaValidate;
function error(text) {
return new ValidationError(text);
}
function validateAddressAndSecret(obj) {
function validateAddressAndSecret(obj: {address: string, secret: string}
): void {
const address = obj.address;
const secret = obj.secret;
schemaValidate('address', address);
if (!secret) {
throw error('Parameter missing: secret');
}
try {
if (!core.Seed.from_json(secret).get_key(address)) {
throw error('secret does not match address');
}
} catch (exception) {
throw error('secret does not match address');
if (!core.Seed.from_json(secret).is_valid()) {
throw error('secret is invalid');
}
}
function validateSecret(secret: string): void {
if (!secret) {
throw error('Parameter missing: secret');
}
if (typeof secret !== 'string' || secret[0] !== 's') {
throw error('Invalid parameter');
}
const seed = new core.Seed().parse_base58(secret);
if (!seed.is_valid()) {
throw error('invalid seed');
}
}
@@ -41,8 +53,10 @@ function validateOptions(schema, options) {
module.exports = {
address: _.partial(schemaValidate, 'address'),
addressAndSecret: validateAddressAndSecret,
secret: validateSecret,
currency: _.partial(schemaValidate, 'currency'),
identifier: _.partial(schemaValidate, 'hash256'),
ledgerVersion: _.partial(schemaValidate, 'ledgerVersion'),
sequence: _.partial(schemaValidate, 'sequence'),
order: _.partial(schemaValidate, 'order'),
orderbook: _.partial(schemaValidate, 'orderbook'),
@@ -60,6 +74,7 @@ module.exports = {
getOrdersOptions: _.partial(validateOptions, 'orders-options'),
getOrderbookOptions: _.partial(validateOptions, 'orders-options'),
getTransactionOptions: _.partial(validateOptions, 'transaction-options'),
getLedgerOptions: _.partial(validateOptions, 'ledger-options'),
options: _.partial(validateOptions, 'options'),
instructions: _.partial(schemaValidate, 'instructions')
};

View File

@@ -2,7 +2,7 @@
'use strict';
const _ = require('lodash');
const core = require('./common').core;
const common = require('./common');
const server = require('./server/server');
const connect = server.connect;
const disconnect = server.disconnect;
@@ -28,11 +28,14 @@ const sign = require('./transaction/sign');
const submit = require('./transaction/submit');
const errors = require('./common').errors;
const convertExceptions = require('./common').convertExceptions;
const generateWallet = convertExceptions(core.Wallet.generate);
const generateAddress = convertExceptions(common.generateAddress);
const computeLedgerHash = require('./offline/ledgerhash');
const getLedger = require('./ledger/ledger');
const isValidAddress = common.isValidAddress;
function RippleAPI(options: {}) {
const _options = _.assign({}, options, {automatic_resubmission: false});
this.remote = new core.Remote(_options);
this.remote = new common.core.Remote(_options);
}
RippleAPI.prototype = {
@@ -52,6 +55,7 @@ RippleAPI.prototype = {
getOrderbook,
getSettings,
getAccountInfo,
getLedger,
preparePayment,
prepareTrustline,
@@ -61,8 +65,17 @@ RippleAPI.prototype = {
sign,
submit,
generateWallet,
computeLedgerHash,
isValidAddress,
generateAddress,
errors
};
// these are exposed only for use by unit tests; they are not part of the API
RippleAPI._PRIVATE = {
common: common,
ledgerUtils: require('./ledger/utils'),
schemaValidator: require('./common/schema-validator')
};
module.exports = RippleAPI;

View File

@@ -6,7 +6,43 @@ const removeUndefined = require('./parse/utils').removeUndefined;
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
function formatAccountInfo(response) {
type AccountData = {
Sequence: number,
Account: string,
Balance: string,
Flags: number,
LedgerEntryType: string,
OwnerCount: number,
PreviousTxnID: string,
AccountTxnID?: string,
PreviousTxnLgrSeq: number,
index: string
}
type AccountDataResponse = {
account_data: AccountData,
ledger_current_index?: number,
ledger_hash?: string,
ledger_index: number,
validated: boolean
}
type AccountInfoOptions = {
ledgerVersion?: number
}
type AccountInfoCallback = (err: any, data: AccountInfoResponse) => void
type AccountInfoResponse = {
sequence: number,
xrpBalance: string,
ownerCount: number,
previousInitiatedTransactionID: string,
previousAffectingTransactionID: string,
previousAffectingTransactionLedgerVersion: number
}
function formatAccountInfo(response: AccountDataResponse) {
const data = response.account_data;
return removeUndefined({
sequence: data.Sequence,
@@ -18,17 +54,24 @@ function formatAccountInfo(response) {
});
}
function getAccountInfo(account, options, callback) {
function getAccountInfoAsync(account: string, options: AccountInfoOptions,
callback: AccountInfoCallback
) {
validate.address(account);
validate.getAccountInfoOptions(options);
const request = {
account: account,
ledger: options.ledgerVersion
ledger: options.ledgerVersion || 'validated'
};
this.remote.requestAccountInfo(request,
composeAsync(formatAccountInfo, callback));
}
module.exports = utils.wrapCatch(getAccountInfo);
function getAccountInfo(account: string, options: AccountInfoOptions={}
): Promise<AccountInfoResponse> {
return utils.promisify(getAccountInfoAsync).call(this, account, options);
}
module.exports = getAccountInfo;

View File

@@ -24,7 +24,13 @@ function formatBalances(balances) {
balances.trustlines.map(getTrustlineBalanceAmount));
}
function getBalances(account, options, callback) {
function getTrustlinesAsync(account, options, callback) {
getTrustlines.call(this, account, options)
.then(data => callback(null, data))
.catch(callback);
}
function getBalancesAsync(account, options, callback) {
validate.address(account);
validate.getBalancesOptions(options);
@@ -32,8 +38,12 @@ function getBalances(account, options, callback) {
|| this.remote.getLedgerSequence();
async.parallel({
xrp: _.partial(utils.getXRPBalance, this.remote, account, ledgerVersion),
trustlines: _.partial(getTrustlines.bind(this), account, options)
trustlines: _.partial(getTrustlinesAsync.bind(this), account, options)
}, composeAsync(formatBalances, callback));
}
module.exports = utils.wrapCatch(getBalances);
function getBalances(account: string, options={}) {
return utils.promisify(getBalancesAsync).call(this, account, options);
}
module.exports = getBalances;

26
src/api/ledger/ledger.js Normal file
View File

@@ -0,0 +1,26 @@
/* @flow */
'use strict';
const utils = require('./utils');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const parseLedger = require('./parse/ledger');
function getLedgerAsync(options, callback) {
validate.getLedgerOptions(options);
const request = {
ledger: options.ledgerVersion || 'validated',
expand: options.includeAllData,
transactions: options.includeTransactions,
accounts: options.includeState
};
this.remote.requestLedger(request,
composeAsync(response => parseLedger(response.ledger), callback));
}
function getLedger(options={}) {
return utils.promisify(getLedgerAsync).call(this, options);
}
module.exports = getLedger;

View File

@@ -10,7 +10,8 @@ const composeAsync = utils.common.composeAsync;
// account is to specify a "perspective", which affects which unfunded offers
// are returned
function getBookOffers(remote, account, ledgerVersion, limit,
takerGets, takerPays, callback) {
takerGets, takerPays, callback
) {
remote.requestBookOffers(utils.renameCounterpartyToIssuerInOrder({
taker_gets: takerGets,
taker_pays: takerPays,
@@ -62,7 +63,7 @@ function formatBidsAndAsks(orderbook, offers) {
return {bids, asks};
}
function getOrderbook(account, orderbook, options, callback) {
function getOrderbookAsync(account, orderbook, options, callback) {
validate.address(account);
validate.orderbook(orderbook);
validate.getOrderbookOptions(options);
@@ -76,4 +77,9 @@ function getOrderbook(account, orderbook, options, callback) {
callback));
}
module.exports = utils.wrapCatch(getOrderbook);
function getOrderbook(account: string, orderbook: Object, options={}) {
return utils.promisify(getOrderbookAsync).call(this,
account, orderbook, options);
}
module.exports = getOrderbook;

View File

@@ -7,7 +7,8 @@ const composeAsync = utils.common.composeAsync;
const parseAccountOrder = require('./parse/account-order');
function requestAccountOffers(remote, address, ledgerVersion, options,
marker, limit, callback) {
marker, limit, callback
) {
remote.requestAccountOffers({
account: address,
marker: marker,
@@ -20,7 +21,7 @@ function requestAccountOffers(remote, address, ledgerVersion, options,
}), callback));
}
function getOrders(account, options, callback) {
function getOrdersAsync(account, options, callback) {
validate.address(account);
validate.getOrdersOptions(options);
@@ -33,4 +34,8 @@ function getOrders(account, options, callback) {
(order) => order.properties.sequence), callback));
}
module.exports = utils.wrapCatch(getOrders);
function getOrders(account: string, options={}) {
return utils.promisify(getOrdersAsync).call(this, account, options);
}
module.exports = getOrders;

View File

@@ -1,11 +1,15 @@
/* @flow */
'use strict';
const BigNumber = require('bignumber.js');
const AccountFields = require('./utils').constants.AccountFields;
function parseField(info, value) {
if (info.encoding === 'hex' && !info.length) {
return new Buffer(value, 'hex').toString('ascii');
}
if (info.shift) {
return (new BigNumber(value)).shift(-info.shift).toNumber();
}
return value;
}

View File

@@ -0,0 +1,50 @@
/* @flow */
'use strict';
const _ = require('lodash');
const removeUndefined = require('./utils').removeUndefined;
const parseTransaction = require('./transaction');
function parseTransactions(transactions) {
if (_.isEmpty(transactions)) {
return {};
}
if (_.isString(transactions[0])) {
return {transactionHashes: transactions};
}
return {
transactions: _.map(transactions, parseTransaction),
rawTransactions: JSON.stringify(transactions)
};
}
function parseState(state) {
if (_.isEmpty(state)) {
return {};
}
if (_.isString(state[0])) {
return {stateHashes: state};
}
return {rawState: JSON.stringify(state)};
}
function parseLedger(ledger: Object): Object {
return removeUndefined(_.assign({
accepted: ledger.accepted,
closed: ledger.closed,
stateHash: ledger.account_hash,
closeTime: ledger.close_time,
closeTimeResolution: ledger.close_time_resolution,
closeFlags: ledger.close_flags,
ledgerHash: ledger.hash || ledger.ledger_hash,
ledgerVersion: parseInt(ledger.ledger_index || ledger.seqNum, 10),
parentLedgerHash: ledger.parent_hash,
parentCloseTime: ledger.parent_close_time,
totalDrops: ledger.total_coins || ledger.totalCoins,
transactionHash: ledger.transaction_hash
},
parseTransactions(ledger.transactions),
parseState(ledger.accountState)
));
}
module.exports = parseLedger;

View File

@@ -1,5 +1,6 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const utils = require('./utils');
const parseAmount = require('./amount');
@@ -30,18 +31,24 @@ function parsePaymentMemos(tx) {
});
}
function removeGenericCounterparty(amount, address) {
return amount.counterparty === address ?
_.omit(amount, 'counterparty') : amount;
}
function parsePayment(tx: Object): Object {
assert(tx.TransactionType === 'Payment');
const source = {
address: tx.Account,
amount: parseAmount(tx.SendMax || tx.Amount),
maxAmount: removeGenericCounterparty(
parseAmount(tx.SendMax || tx.Amount), tx.Account),
tag: tx.SourceTag
};
const destination = {
address: tx.Destination,
amount: parseAmount(tx.Amount),
amount: removeGenericCounterparty(parseAmount(tx.Amount), tx.Destination),
tag: tx.DestinationTag
};

View File

@@ -3,6 +3,7 @@
const assert = require('assert');
const utils = require('./utils');
const flags = utils.core.Transaction.flags.TrustSet;
const BigNumber = require('bignumber.js');
function parseFlag(flagsValue, trueValue, falseValue) {
if (flagsValue & trueValue) {
@@ -14,6 +15,13 @@ function parseFlag(flagsValue, trueValue, falseValue) {
return undefined;
}
function parseQuality(quality?: number) {
if (typeof quality === 'number') {
return (new BigNumber(quality)).shift(-9).toNumber();
}
return undefined;
}
function parseTrustline(tx: Object): Object {
assert(tx.TransactionType === 'TrustSet');
@@ -21,8 +29,8 @@ function parseTrustline(tx: Object): Object {
limit: tx.LimitAmount.value,
currency: tx.LimitAmount.currency,
counterparty: tx.LimitAmount.issuer,
qualityIn: tx.QualityIn,
qualityOut: tx.QualityOut,
qualityIn: parseQuality(tx.QualityIn),
qualityOut: parseQuality(tx.QualityOut),
ripplingDisabled: parseFlag(
tx.Flags, flags.SetNoRipple, flags.ClearNoRipple),
frozen: parseFlag(tx.Flags, flags.SetFreeze, flags.ClearFreeze),

View File

@@ -6,8 +6,9 @@ const toTimestamp = require('../../../core/utils').toTimestamp;
const utils = require('../utils');
const BigNumber = require('bignumber.js');
function adjustQualityForXRP(quality: string, takerGetsCurrency: string,
takerPaysCurrency: string) {
function adjustQualityForXRP(
quality: string, takerGetsCurrency: string, takerPaysCurrency: string
) {
// quality = takerPays.value/takerGets.value
// using drops (1e-6 XRP) for XRP values
const numeratorShift = (takerPaysCurrency === 'XRP' ? -6 : 0);
@@ -51,7 +52,7 @@ function parseOutcome(tx: Object): ?Object {
}
const balanceChanges = transactionParser.parseBalanceChanges(tx.meta);
const orderbookChanges = transactionParser.parseOrderBookChanges(tx.meta);
const orderbookChanges = transactionParser.parseOrderbookChanges(tx.meta);
removeEmptyCounterpartyInBalanceChanges(balanceChanges);
removeEmptyCounterpartyInOrderbookChanges(orderbookChanges);

View File

@@ -46,7 +46,7 @@ function requestPathFind(remote, pathfind: PathFind, callback) {
_.omit(utils.common.toRippledAmount(amount), 'value'));
}
remote.requestRipplePathFind(params,
remote.createPathFind(params,
composeAsync(_.partial(addParams, params), callback));
}
@@ -103,7 +103,7 @@ function formatResponse(pathfind, paths) {
}
}
function getPaths(pathfind, callback) {
function getPathsAsync(pathfind, callback) {
validate.pathfind(pathfind);
const address = pathfind.source.address;
@@ -113,4 +113,8 @@ function getPaths(pathfind, callback) {
], composeAsync(_.partial(formatResponse, pathfind), callback));
}
module.exports = utils.wrapCatch(getPaths);
function getPaths(pathfind: Object) {
return utils.promisify(getPathsAsync).call(this, pathfind);
}
module.exports = getPaths;

View File

@@ -24,17 +24,21 @@ function formatSettings(response) {
return _.assign({}, parsedFlags, parsedFields);
}
function getSettings(account, options, callback) {
function getSettingsAsync(account, options, callback) {
validate.address(account);
validate.getSettingsOptions(options);
const request = {
account: account,
ledger: options.ledgerVersion
ledger: options.ledgerVersion || 'validated'
};
this.remote.requestAccountInfo(request,
composeAsync(formatSettings, callback));
}
module.exports = utils.wrapCatch(getSettings);
function getSettings(account: string, options={}) {
return utils.promisify(getSettingsAsync).call(this, account, options);
}
module.exports = getSettings;

View File

@@ -0,0 +1,147 @@
/* @flow */
'use strict';
type Outcome = {
result: string,
timestamp?: string,
fee: string,
balanceChanges: Object,
orderbookChanges: Object,
ledgerVersion: number,
indexInLedger: number
}
type Adjustment = {
address: string,
amount: {
currency: string,
counterparty?: string,
value: string
},
tag?: number
}
type Trustline = {
currency: string,
counterparty: string,
limit: string,
qualityIn?: number,
qualityOut?: number,
ripplingDisabled?: boolean,
authorized?: boolean,
frozen?: boolean
}
type Settings = {
passwordSpent?: boolean,
requireDestinationTag?: boolean,
requireAuthorization?: boolean,
disallowIncomingXRP?: boolean,
disableMasterKey?: boolean,
enableTransactionIDTracking?: boolean,
noFreeze?: boolean,
globalFreeze?: boolean,
defaultRipple?: boolean,
emailHash?: string,
walletLocator?: string,
walletSize?: number,
messageKey?: string,
domain?: string,
transferRate?: number,
signers?: string,
regularKey?: string
}
type OrderCancellation = {
orderSequence: number
}
type Memo = {
type?: string,
format?: string,
data?: string
}
type Amount = {
value: string,
currency: string,
counterparty?: string
}
type Payment = {
source: Adjustment,
destination: Adjustment,
paths?: string,
memos?: Array<Memo>,
invoiceID?: string,
allowPartialPayment?: boolean,
noDirectRipple?: boolean,
limitQuality?: boolean
}
type PaymentTransaction = {
type: string,
specification: Payment,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type Order = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
passive?: boolean
}
type OrderTransaction = {
type: string,
specification: Order,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type OrderCancellationTransaction = {
type: string,
specification: OrderCancellation,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type TrustlineTransaction = {
type: string,
specification: Trustline,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type SettingsTransaction = {
type: string,
specification: Settings,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type TransactionOptions = {
minLedgerVersion?: number,
maxLedgerVersion?: number
}
export type GetTransactionResponse = PaymentTransaction | OrderTransaction |
OrderCancellationTransaction | TrustlineTransaction | SettingsTransaction
export type GetTransactionResponseCallback =
(err?: ?Error, data?: GetTransactionResponse) => void
export type CallbackType = (err?: ?Error, data?: Object) => void

View File

@@ -6,8 +6,17 @@ const utils = require('./utils');
const parseTransaction = require('./parse/transaction');
const validate = utils.common.validate;
const errors = utils.common.errors;
const RippleError = require('../../core/rippleerror').RippleError;
function attachTransactionDate(remote, tx, callback) {
import type {Remote} from '../../core/remote';
import type {CallbackType, GetTransactionResponse,
GetTransactionResponseCallback, TransactionOptions}
from './transaction-types';
function attachTransactionDate(remote: Remote, tx: Object,
callback: CallbackType
) {
if (tx.date) {
callback(null, tx);
return;
@@ -28,40 +37,57 @@ function attachTransactionDate(remote, tx, callback) {
});
}
function isTransactionInRange(tx, options) {
function isTransactionInRange(tx: Object, options: TransactionOptions) {
return (!options.minLedgerVersion
|| tx.ledger_index >= options.minLedgerVersion)
&& (!options.maxLedgerVersion
|| tx.ledger_index <= options.maxLedgerVersion);
}
function getTransaction(identifier, options, callback) {
function getTransactionAsync(identifier: string, options: TransactionOptions,
callback: GetTransactionResponseCallback
) {
validate.identifier(identifier);
validate.getTransactionOptions(options);
const remote = this.remote;
const maxLedgerVersion = Math.min(options.maxLedgerVersion,
const maxLedgerVersion = Math.min(options.maxLedgerVersion || Infinity,
remote.getLedgerSequence());
function callbackWrapper(error, tx) {
function callbackWrapper(error_?: Error, tx?: Object) {
let error = error_;
if (error instanceof RippleError && error.remote &&
error.remote.error === 'txnNotFound') {
error = new errors.NotFoundError('Transaction not found');
}
if (error instanceof errors.NotFoundError
&& !utils.hasCompleteLedgerRange(remote,
options.minLedgerVersion, maxLedgerVersion)) {
callback(new errors.MissingLedgerHistoryError('Transaction not found,'
+ ' but the server\'s ledger history is incomplete'));
} else if (!error && !isTransactionInRange(tx, options)) {
} else if (!error && tx && !isTransactionInRange(tx, options)) {
callback(new errors.NotFoundError('Transaction not found'));
} else if (error) {
callback(error);
} else if (!tx) {
callback(new Error('Internal error'));
} else {
callback(error, parseTransaction(tx));
}
}
async.waterfall([
_.partial(remote.requestTx.bind(remote), {hash: identifier, binary: false}),
_.partial(remote.requestTx.bind(remote),
{hash: identifier, binary: false}),
_.partial(attachTransactionDate, remote)
], callbackWrapper);
}
module.exports = utils.wrapCatch(getTransaction);
function getTransaction(identifier: string,
options: TransactionOptions={}
): Promise<GetTransactionResponse> {
return utils.promisify(getTransactionAsync).call(this, identifier, options);
}
module.exports = getTransaction;

View File

@@ -1,4 +1,5 @@
/* @flow */
/* eslint-disable max-params */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
@@ -14,6 +15,21 @@ function parseAccountTxTransaction(tx) {
return parseTransaction(tx.tx);
}
function counterpartyFilter(filters, tx) {
if (!filters.counterparty) {
return true;
}
if (tx.address === filters.counterparty || (
tx.specification && (
(tx.specification.destination &&
tx.specification.destination.address === filters.counterparty) ||
(tx.specification.counterparty === filters.counterparty)
))) {
return true;
}
return false;
}
function transactionFilter(address, filters, tx) {
if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') {
return false;
@@ -27,8 +43,7 @@ function transactionFilter(address, filters, tx) {
if (filters.initiated === false && tx.address === address) {
return false;
}
if (filters.counterparty && tx.address !== filters.counterparty
&& tx.Destination !== filters.counterparty) {
if (filters.counterparty && !counterpartyFilter(filters, tx)) {
return false;
}
return true;
@@ -97,27 +112,27 @@ function getTransactionsInternal(remote, address, options, callback) {
utils.getRecursive(getter, options.limit, composeAsync(format, callback));
}
function getTransactions(address, options, callback) {
validate.address(address);
function getTransactionsAsync(account, options, callback) {
validate.address(account);
validate.getTransactionsOptions(options);
const defaults = {maxLedgerVersion: this.remote.getLedgerSequence()};
if (options.start) {
getTransaction.bind(this)(options.start, {}, (error, tx) => {
if (error) {
callback(error);
return;
}
getTransaction.call(this, options.start).then(tx => {
const ledgerVersion = tx.outcome.ledgerVersion;
const bound = options.earliestFirst ?
{minLedgerVersion: ledgerVersion} : {maxLedgerVersion: ledgerVersion};
const newOptions = _.assign(defaults, options, {startTx: tx}, bound);
getTransactionsInternal(this.remote, address, newOptions, callback);
});
getTransactionsInternal(this.remote, account, newOptions, callback);
}).catch(callback);
} else {
const newOptions = _.assign(defaults, options);
getTransactionsInternal(this.remote, address, newOptions, callback);
getTransactionsInternal(this.remote, account, newOptions, callback);
}
}
module.exports = utils.wrapCatch(getTransactions);
function getTransactions(account: string, options={}) {
return utils.promisify(getTransactionsAsync).call(this, account, options);
}
module.exports = getTransactions;

View File

@@ -10,7 +10,8 @@ function currencyFilter(currency, trustline) {
}
function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
callback) {
callback
) {
const requestOptions = {
account: address,
ledger: ledgerVersion,
@@ -29,9 +30,10 @@ function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
});
}
function getTrustlines(account: string, options: {currency: string,
function getTrustlinesAsync(account: string, options: {currency: string,
counterparty: string, limit: number, ledgerVersion: number},
callback: () => void): void {
callback: () => void
): void {
validate.address(account);
validate.getTrustlinesOptions(options);
@@ -42,4 +44,8 @@ function getTrustlines(account: string, options: {currency: string,
utils.getRecursive(getter, options.limit, callback);
}
module.exports = utils.wrapCatch(getTrustlines);
function getTrustlines(account: string, options={}) {
return utils.promisify(getTrustlinesAsync).call(this, account, options);
}
module.exports = getTrustlines;

View File

@@ -1,23 +1,33 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const common = require('../common');
const dropsToXrp = common.dropsToXrp;
const composeAsync = common.composeAsync;
import type {Remote} from '../../core/remote';
function clamp(value, min, max) {
type Callback = (err: any, data: any) => void
function clamp(value: number, min: number, max: number): number {
assert(min <= max, 'Illegal clamp bounds');
return Math.min(Math.max(value, min), max);
}
function getXRPBalance(remote, address, ledgerVersion, callback) {
function getXRPBalance(remote: Remote, address: string, ledgerVersion?: number,
callback: Callback
): void {
remote.requestAccountInfo({account: address, ledger: ledgerVersion},
composeAsync((data) => dropsToXrp(data.account_data.Balance), callback));
}
type Getter = (marker: ?string, limit: number, callback: Callback) => void
// If the marker is omitted from a response, you have reached the end
// getter(marker, limit, callback), callback(error, {marker, results})
function getRecursiveRecur(getter, marker, limit, callback) {
function getRecursiveRecur(getter: Getter, marker?: string, limit: number,
callback: Callback
): void {
getter(marker, limit, (error, data) => {
if (error) {
return callback(error);
@@ -34,11 +44,13 @@ function getRecursiveRecur(getter, marker, limit, callback) {
});
}
function getRecursive(getter, limit, callback) {
function getRecursive(getter: Getter, limit?: number, callback: Callback) {
getRecursiveRecur(getter, undefined, limit || Infinity, callback);
}
function renameCounterpartyToIssuer(amount) {
type Amount = {counterparty?: string, issuer?: string, value: string}
function renameCounterpartyToIssuer(amount?: Amount): ?{issuer?: string} {
if (amount === undefined) {
return undefined;
}
@@ -48,7 +60,9 @@ function renameCounterpartyToIssuer(amount) {
return _.omit(withIssuer, 'counterparty');
}
function renameCounterpartyToIssuerInOrder(order) {
type Order = {taker_gets: Amount, taker_pays: Amount}
function renameCounterpartyToIssuerInOrder(order: Order) {
const taker_gets = renameCounterpartyToIssuer(order.taker_gets);
const taker_pays = renameCounterpartyToIssuer(order.taker_pays);
const changes = {taker_gets: taker_gets, taker_pays: taker_pays};
@@ -70,14 +84,21 @@ function signum(num) {
* @returns {Number} [-1, 0, 1]
*/
function compareTransactions(first, second) {
if (first.ledgerVersion === second.ledgerVersion) {
return signum(Number(first.indexInLedger) - Number(second.indexInLedger));
type Outcome = {outcome: {ledgerVersion: number, indexInLedger: number}};
function compareTransactions(first: Outcome, second: Outcome): number {
if (!first.outcome || !second.outcome) {
return 0;
}
return Number(first.ledgerVersion) < Number(second.ledgerVersion) ? -1 : 1;
if (first.outcome.ledgerVersion === second.outcome.ledgerVersion) {
return signum(first.outcome.indexInLedger - second.outcome.indexInLedger);
}
return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1;
}
function hasCompleteLedgerRange(remote, minLedgerVersion, maxLedgerVersion) {
function hasCompleteLedgerRange(remote: Remote, minLedgerVersion?: number,
maxLedgerVersion?: number
): boolean {
const firstLedgerVersion = 32570; // earlier versions have been lost
return remote.getServer().hasLedgerRange(
minLedgerVersion || firstLedgerVersion,
@@ -91,7 +112,7 @@ module.exports = {
renameCounterpartyToIssuerInOrder,
getRecursive,
hasCompleteLedgerRange,
wrapCatch: common.wrapCatch,
promisify: common.promisify,
clamp: clamp,
common: common
};

View File

@@ -0,0 +1,74 @@
/* @flow */
'use strict';
const _ = require('lodash');
const common = require('../common');
function convertLedgerHeader(header) {
return {
accepted: header.accepted,
closed: header.closed,
account_hash: header.stateHash,
close_time: header.closeTime,
close_time_resolution: header.closeTimeResolution,
close_flags: header.closeFlags,
hash: header.ledgerHash,
ledger_hash: header.ledgerHash,
ledger_index: header.ledgerVersion.toString(),
seqNum: header.ledgerVersion.toString(),
parent_hash: header.parentLedgerHash,
parent_close_time: header.parentCloseTime,
total_coins: header.totalDrops,
totalCoins: header.totalDrops,
transaction_hash: header.transactionHash
};
}
function hashLedgerHeader(ledgerHeader) {
const header = convertLedgerHeader(ledgerHeader);
return common.core.Ledger.calculateLedgerHash(header);
}
function computeTransactionHash(ledger) {
if (ledger.rawTransactions === undefined) {
return ledger.transactionHash;
}
const transactions = JSON.parse(ledger.rawTransactions);
const txs = _.map(transactions, tx => {
const mergeTx = _.assign({}, _.omit(tx, 'tx'), tx.tx || {});
const renameMeta = _.assign({}, _.omit(mergeTx, 'meta'),
tx.meta ? {metaData: tx.meta} : {});
return renameMeta;
});
const ledgerObject = common.core.Ledger.from_json({transactions: txs});
const transactionHash = ledgerObject.calc_tx_hash().to_hex();
if (ledger.transactionHash !== undefined
&& ledger.transactionHash !== transactionHash) {
throw new common.errors.ValidationError('transactionHash in header'
+ ' does not match computed hash of transactions');
}
return transactionHash;
}
function computeStateHash(ledger) {
if (ledger.rawState === undefined) {
return ledger.stateHash;
}
const state = JSON.parse(ledger.rawState);
const ledgerObject = common.core.Ledger.from_json({accountState: state});
const stateHash = ledgerObject.calc_account_hash().to_hex();
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
throw new common.errors.ValidationError('stateHash in header'
+ ' does not match computed hash of state');
}
return stateHash;
}
function computeLedgerHash(ledger: Object): string {
const hashes = {
transactionHash: computeTransactionHash(ledger),
stateHash: computeStateHash(ledger)
};
return hashLedgerHeader(_.assign({}, ledger, hashes));
}
module.exports = computeLedgerHash;

View File

@@ -2,20 +2,43 @@
'use strict';
const _ = require('lodash');
const common = require('../common');
import type {Remote} from '../../core/remote';
// If a ledger is not received in this time, consider the connection offline
const CONNECTION_TIMEOUT = 1000 * 30;
function connect(callback: (err: any, data: any) => void): void {
this.remote.connect(callback);
type GetServerInfoResponse = {
buildVersion: string,
completeLedgers: string,
hostid: string,
ioLatencyMs: number,
load?: {
jobTypes: Array<Object>,
threads: number
},
lastClose: {
convergeTimeS: number,
proposers: number
},
loadFactor: number,
peers: number,
pubkeyNode: string,
pubkeyValidator?: string,
serverState: string,
validatedLedger: {
age: number,
baseFeeXrp: number,
hash: string,
reserveBaseXrp: number,
reserveIncXrp: number,
seq: number
},
validationQuorum: number
}
function disconnect(callback: (err: any, data: any) => void): void {
this.remote.disconnect(callback);
}
function isUpToDate(remote): boolean {
function isUpToDate(remote: Remote): boolean {
const server = remote.getServer();
return Boolean(server) && (remote._stand_alone
|| (Date.now() - server._lastLedgerClose) <= CONNECTION_TIMEOUT);
@@ -25,12 +48,17 @@ function isConnected(): boolean {
return Boolean(this.remote._ledger_current_index) && isUpToDate(this.remote);
}
function getServerInfo(callback: (err: any, data: any) => void): void {
function getServerInfoAsync(
callback: (err: any, data?: GetServerInfoResponse) => void
): void {
this.remote.requestServerInfo((error, response) => {
if (error) {
callback(new common.errors.RippledNetworkError(error.message));
const message =
_.get(error, ['remote', 'error_message'], error.message);
callback(new common.errors.RippledNetworkError(message));
} else {
callback(null, response.info);
callback(null,
common.convertKeysFromSnakeCaseToCamelCase(response.info));
}
});
}
@@ -43,6 +71,22 @@ function getLedgerVersion(): number {
return this.remote.getLedgerSequence();
}
function connect(): Promise<void> {
return common.promisify(callback => {
this.remote.connect(() => callback(null));
})();
}
function disconnect(): Promise<void> {
return common.promisify(callback => {
this.remote.disconnect(() => callback(null));
})();
}
function getServerInfo(): Promise<GetServerInfoResponse> {
return common.promisify(getServerInfoAsync.bind(this))();
}
module.exports = {
connect,
disconnect,

View File

@@ -30,9 +30,14 @@ function createOrderTransaction(account, order) {
return transaction;
}
function prepareOrder(account, order, instructions, callback) {
function prepareOrderAsync(account, order, instructions, callback) {
const transaction = createOrderTransaction(account, order);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(prepareOrder);
function prepareOrder(account: string, order: Object, instructions={}) {
return utils.promisify(prepareOrderAsync.bind(this))(
account, order, instructions);
}
module.exports = prepareOrder;

View File

@@ -13,9 +13,16 @@ function createOrderCancellationTransaction(account, sequence) {
return transaction;
}
function prepareOrderCancellation(account, sequence, instructions, callback) {
function prepareOrderCancellationAsync(account, sequence, instructions,
callback) {
const transaction = createOrderCancellationTransaction(account, sequence);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(prepareOrderCancellation);
function prepareOrderCancellation(account: string, sequence: number,
instructions={}) {
return utils.promisify(prepareOrderCancellationAsync.bind(this))(
account, sequence, instructions);
}
module.exports = prepareOrderCancellation;

View File

@@ -1,21 +1,15 @@
/* @flow */
'use strict';
const _ = require('lodash');
const BigNumber = require('bignumber.js');
const utils = require('./utils');
const validate = utils.common.validate;
const toRippledAmount = utils.common.toRippledAmount;
const Transaction = utils.common.core.Transaction;
function isSendMaxAllowed(payment) {
const srcAmt = payment.source.amount;
const dstAmt = payment.destination.amount;
// Don't set SendMax for XRP->XRP payment
// temREDUNDANT_SEND_MAX removed in:
// https://github.com/ripple/rippled/commit/
// c522ffa6db2648f1d8a987843e7feabf1a0b7de8/
return srcAmt && !(srcAmt.currency === 'XRP' && dstAmt.currency === 'XRP');
function isXRPToXRPPayment(payment) {
const sourceCurrency = _.get(payment, 'source.maxAmount.currency');
const destinationCurrency = _.get(payment, 'destination.amount.currency');
return sourceCurrency === 'XRP' && destinationCurrency === 'XRP';
}
function isIOUWithoutCounterparty(amount) {
@@ -29,8 +23,8 @@ function applyAnyCounterpartyEncoding(payment) {
// https://ripple.com/build/transactions/
// #special-issuer-values-for-sendmax-and-amount
// https://ripple.com/build/ripple-rest/#counterparties-in-payments
if (isIOUWithoutCounterparty(payment.source.amount)) {
payment.source.amount.counterparty = payment.source.address;
if (isIOUWithoutCounterparty(payment.source.maxAmount)) {
payment.source.maxAmount.counterparty = payment.source.address;
}
if (isIOUWithoutCounterparty(payment.destination.amount)) {
payment.destination.amount.counterparty = payment.destination.address;
@@ -58,9 +52,6 @@ function createPaymentTransaction(account, payment) {
if (payment.destination.tag) {
transaction.destinationTag(payment.destination.tag);
}
if (payment.paths) {
transaction.paths(JSON.parse(payment.paths));
}
if (payment.memos) {
_.forEach(payment.memos, memo =>
transaction.addMemo(memo.type, memo.format, memo.data)
@@ -75,19 +66,29 @@ function createPaymentTransaction(account, payment) {
if (payment.limitQuality) {
transaction.setFlags(['LimitQuality']);
}
if (isSendMaxAllowed(payment)) {
const maxValue = new BigNumber(payment.source.amount.value)
.plus(payment.source.slippage || 0).toString();
const maxAmount = _.assign({}, payment.source.amount, {value: maxValue});
transaction.sendMax(toRippledAmount(maxAmount));
if (!isXRPToXRPPayment(payment)) {
// Don't set SendMax for XRP->XRP payment
// temREDUNDANT_SEND_MAX removed in:
// https://github.com/ripple/rippled/commit/
// c522ffa6db2648f1d8a987843e7feabf1a0b7de8/
transaction.sendMax(toRippledAmount(payment.source.maxAmount));
if (payment.paths) {
transaction.paths(JSON.parse(payment.paths));
}
}
return transaction;
}
function preparePayment(account, payment, instructions, callback) {
function preparePaymentAsync(account, payment, instructions, callback) {
const transaction = createPaymentTransaction(account, payment);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(preparePayment);
function preparePayment(account: string, payment: Object, instructions={}) {
return utils.promisify(preparePaymentAsync.bind(this))(
account, payment, instructions);
}
module.exports = preparePayment;

View File

@@ -1,7 +1,7 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const BigNumber = require('bignumber.js');
const utils = require('./utils');
const validate = utils.common.validate;
const AccountFlagIndices = utils.common.constants.AccountFlagIndices;
@@ -9,7 +9,7 @@ const AccountFields = utils.common.constants.AccountFields;
const Transaction = utils.common.core.Transaction;
// Emptry string passed to setting will clear it
const CLEAR_SETTING = '';
const CLEAR_SETTING = null;
function setTransactionFlags(transaction, values) {
const keys = Object.keys(values);
@@ -64,7 +64,7 @@ function setTransactionFields(transaction, input) {
*/
function convertTransferRate(transferRate) {
return _.isNumber(transferRate) ? transferRate * 1e9 : transferRate;
return (new BigNumber(transferRate)).shift(9).toNumber();
}
function createSettingsTransaction(account, settings) {
@@ -90,9 +90,14 @@ function createSettingsTransaction(account, settings) {
return transaction;
}
function prepareSettings(account, settings, instructions, callback) {
function prepareSettingsAsync(account, settings, instructions, callback) {
const transaction = createSettingsTransaction(account, settings);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(prepareSettings);
function prepareSettings(account: string, settings: Object, instructions={}) {
return utils.promisify(prepareSettingsAsync.bind(this))(
account, settings, instructions);
}
module.exports = prepareSettings;

View File

@@ -15,15 +15,13 @@ const validate = utils.common.validate;
* some arbitrary string. For example "TXN".
*/
const HASH_TX_ID = 0x54584E00; // 'TXN'
const HASH_TX_SIGN = 0x53545800; // 'STX'
const HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
function getKeyPair(address, secret) {
return core.Seed.from_json(secret).get_key(address);
function getKeyPair(secret) {
return core.Seed.from_json(secret).get_key();
}
function getPublicKeyHex(keypair) {
return keypair.to_hex_pub();
return keypair.pubKeyHex();
}
function serialize(txJSON) {
@@ -34,26 +32,21 @@ function hashSerialization(serialized, prefix) {
return serialized.hash(prefix || HASH_TX_ID).to_hex();
}
function hashJSON(txJSON, prefix) {
return hashSerialization(serialize(txJSON), prefix);
}
function signingHash(txJSON, isTestNet=false) {
return hashJSON(txJSON, isTestNet ? HASH_TX_SIGN_TESTNET : HASH_TX_SIGN);
function signingData(txJSON) {
return core.Transaction.from_json(txJSON).signingData().buffer;
}
function computeSignature(txJSON, keypair) {
const signature = keypair.sign(signingHash(txJSON));
return core.sjcl.codec.hex.fromBits(signature).toUpperCase();
return keypair.signHex(signingData(txJSON));
}
function sign(txJSON: {Account: string; SigningPubKey: string,
TxnSignature: string}, secret: string):
{signedTransaction: string; id: string} {
validate.txJSON(txJSON);
validate.addressAndSecret({address: txJSON.Account, secret: secret});
validate.secret(secret);
const keypair = getKeyPair(txJSON.Account, secret);
const keypair = getKeyPair(secret);
if (txJSON.SigningPubKey === undefined) {
txJSON.SigningPubKey = getPublicKeyHex(keypair);
}

View File

@@ -4,12 +4,19 @@ const utils = require('./utils');
const validate = utils.common.validate;
const Request = utils.common.core.Request;
function submit(tx_blob: string,
callback: (err: any, data: any) => void): void {
validate.blob(tx_blob);
function submitAsync(txBlob: string, callback: (err: any, data: any) => void):
void {
validate.blob(txBlob);
const request = new Request(this.remote, 'submit');
request.message.tx_blob = tx_blob;
request.request(null, callback);
request.message.tx_blob = txBlob;
request.request(null,
utils.common.composeAsync(
data => utils.common.convertKeysFromSnakeCaseToCamelCase(data),
callback));
}
function submit(txBlob: string) {
return utils.promisify(submitAsync.bind(this))(txBlob);
}
module.exports = submit;

View File

@@ -3,13 +3,19 @@
const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
const BigNumber = require('bignumber.js');
const TrustSetFlags = {
authorized: {set: 'SetAuth'},
allowRippling: {set: 'ClearNoRipple', unset: 'NoRipple'},
ripplingDisabled: {set: 'NoRipple', unset: 'ClearNoRipple'},
frozen: {set: 'SetFreeze', unset: 'ClearFreeze'}
};
function convertQuality(quality) {
return quality === undefined ? undefined :
(new BigNumber(quality)).shift(9).truncated().toNumber();
}
function createTrustlineTransaction(account, trustline) {
validate.address(account);
validate.trustline(trustline);
@@ -21,15 +27,20 @@ function createTrustlineTransaction(account, trustline) {
};
const transaction = new Transaction();
transaction.trustSet(account, limit,
trustline.qualityIn, trustline.qualityOut);
transaction.trustSet(account, limit, convertQuality(trustline.qualityIn),
convertQuality(trustline.qualityOut));
utils.setTransactionBitFlags(transaction, trustline, TrustSetFlags);
return transaction;
}
function prepareTrustline(account, trustline, instructions, callback) {
function prepareTrustlineAsync(account, trustline, instructions, callback) {
const transaction = createTrustlineTransaction(account, trustline);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(prepareTrustline);
function prepareTrustline(account: string, trustline: Object, instructions={}) {
return utils.promisify(prepareTrustlineAsync.bind(this))(
account, trustline, instructions);
}
module.exports = prepareTrustline;

View File

@@ -65,6 +65,6 @@ function createTxJSON(transaction: any, remote: any, instructions: any,
module.exports = {
setTransactionBitFlags: setTransactionBitFlags,
createTxJSON: createTxJSON,
wrapCatch: common.wrapCatch,
common: common
common: common,
promisify: common.promisify
};

View File

@@ -12,13 +12,14 @@
const _ = require('lodash');
const async = require('async');
const util = require('util');
const extend = require('extend');
const EventEmitter = require('events').EventEmitter;
const UInt160 = require('./uint160').UInt160;
const TransactionManager = require('./transactionmanager').TransactionManager;
const sjcl = require('./utils').sjcl;
const Base = require('./base').Base;
const util = require('util');
const {createAccountID} = require('ripple-keypairs');
const {encodeAccountID} = require('ripple-address-codec');
const {EventEmitter} = require('events');
const {hexToArray} = require('./utils');
const {TransactionManager} = require('./transactionmanager');
const {UInt160} = require('./uint160');
/**
* @constructor Account
@@ -41,7 +42,7 @@ function Account(remote, account) {
this._entry = { };
function listenerAdded(type) {
if (~Account.subscribeEvents.indexOf(type)) {
if (_.includes(Account.subscribeEvents, type)) {
if (!self._subs && self._remote._connected) {
self._remote.requestSubscribe()
.addAccount(self._account_id)
@@ -54,7 +55,7 @@ function Account(remote, account) {
this.on('newListener', listenerAdded);
function listenerRemoved(type) {
if (~Account.subscribeEvents.indexOf(type)) {
if (_.includes(Account.subscribeEvents, type)) {
self._subs -= 1;
if (!self._subs && self._remote._connected) {
self._remote.requestUnsubscribe()
@@ -68,7 +69,7 @@ function Account(remote, account) {
function attachAccount(request) {
if (self._account.is_valid() && self._subs) {
request.add_account(self._account_id);
request.addAccount(self._account_id);
}
}
@@ -376,12 +377,7 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
Account._publicKeyToAddress = function(public_key) {
// Based on functions in /src/js/ripple/keypair.js
function hexToUInt160(publicKey) {
const bits = sjcl.codec.hex.toBits(publicKey);
const hash = sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
const address = UInt160.from_bits(hash);
address.set_version(Base.VER_ACCOUNT_ID);
return address.to_json();
return encodeAccountID(createAccountID(hexToArray(publicKey)));
}
if (UInt160.is_valid(public_key)) {

View File

@@ -1,18 +1,11 @@
'use strict';
const _ = require('lodash');
const sjcl = require('./utils').sjcl;
const utils = require('./utils');
const BN = require('bn.js');
const extend = require('extend');
const convertBase = require('./baseconverter');
const {encode, decode} = require('ripple-address-codec');
const Base = {};
const alphabets = Base.alphabets = {
ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz',
bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
};
extend(Base, {
VER_NONE: 1,
VER_NODE_PUBLIC: 28,
@@ -21,134 +14,44 @@ extend(Base, {
VER_ACCOUNT_PUBLIC: 35,
VER_ACCOUNT_PRIVATE: 34,
VER_FAMILY_GENERATOR: 41,
VER_FAMILY_SEED: 33
});
function sha256(bytes) {
return sjcl.codec.bytes.fromBits(
sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
}
function encodeString(alphabet, input) {
if (input.length === 0) {
return '';
}
const leadingZeros = _.takeWhile(input, function(d) {
return d === 0;
});
const out = convertBase(input, 256, 58).map(function(digit) {
if (digit < 0 || digit >= alphabet.length) {
throw new Error('Value ' + digit + ' is out of bounds for encoding');
}
return alphabet[digit];
});
const prefix = leadingZeros.map(function() {
return alphabet[0];
});
return prefix.concat(out).join('');
}
function decodeString(indexes, input) {
if (input.length === 0) {
return [];
}
const input58 = input.split('').map(function(c) {
const charCode = c.charCodeAt(0);
if (charCode >= indexes.length || indexes[charCode] === -1) {
throw new Error('Character ' + c + ' is not valid for encoding');
}
return indexes[charCode];
});
const leadingZeros = _.takeWhile(input58, function(d) {
return d === 0;
});
const out = convertBase(input58, 58, 256);
return leadingZeros.concat(out);
}
function Base58(alphabet) {
const indexes = utils.arraySet(128, -1);
for (let i = 0; i < alphabet.length; i++) {
indexes[alphabet.charCodeAt(i)] = i;
}
return {
decode: decodeString.bind(null, indexes),
encode: encodeString.bind(null, alphabet)
};
}
Base.encoders = {};
Object.keys(alphabets).forEach(function(alphabet) {
Base.encoders[alphabet] = new Base58(alphabets[alphabet]);
VER_FAMILY_SEED: 33,
VER_ED25519_SEED: [0x01, 0xE1, 0x4B]
});
// --> input: big-endian array of bytes.
// <-- string at least as long as input.
Base.encode = function(input, alpha) {
return this.encoders[alpha || 'ripple'].encode(input);
Base.encode = function(input, alphabet) {
return encode(input, {alphabet});
};
// --> input: String
// <-- array of bytes or undefined.
Base.decode = function(input, alpha) {
Base.decode = function(input, alphabet) {
if (typeof input !== 'string') {
return undefined;
}
try {
return this.encoders[alpha || 'ripple'].decode(input);
return decode(input, {alphabet});
} catch (e) {
return undefined;
}
};
Base.verify_checksum = function(bytes) {
const computed = sha256(sha256(bytes.slice(0, -4))).slice(0, 4);
const checksum = bytes.slice(-4);
return _.isEqual(computed, checksum);
};
// --> input: Array
// <-- String
Base.encode_check = function(version, input, alphabet) {
const buffer = [].concat(version, input);
const check = sha256(sha256(buffer)).slice(0, 4);
return Base.encode([].concat(buffer, check), alphabet);
return encode(input, {version, alphabet});
};
// --> input : String
// <-- NaN || sjcl.bn
// <-- NaN || BN
Base.decode_check = function(version, input, alphabet) {
const buffer = Base.decode(input, alphabet);
if (!buffer || buffer.length < 5) {
try {
const decoded = decode(input, {version, alphabet});
return new BN(decoded);
} catch (e) {
return NaN;
}
// Single valid version
if (typeof version === 'number' && buffer[0] !== version) {
return NaN;
}
// Multiple allowed versions
if (Array.isArray(version) && _.every(version, function(v) {
return v !== buffer[0];
})) {
return NaN;
}
if (!Base.verify_checksum(buffer)) {
return NaN;
}
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
// intrepret the value as a negative number
buffer[0] = 0;
return sjcl.bn.fromBits(
sjcl.codec.bytes.toBits(buffer.slice(0, -4)));
};
exports.Base = Base;

View File

@@ -400,6 +400,7 @@ exports.ledger = {
};
exports.metadata = [
[ 'DeliveredAmount' , OPTIONAL ],
[ 'TransactionIndex' , REQUIRED ],
[ 'TransactionResult' , REQUIRED ],
[ 'AffectedNodes' , REQUIRED ]

View File

@@ -27,41 +27,4 @@ exports._test = {
RangeSet: require('./rangeset').RangeSet
};
// Important: We do not guarantee any specific version of SJCL or for any
// specific features to be included. The version and configuration may change at
// any time without warning.
//
// However, for programs that are tied to a specific version of ripple.js like
// the official client, it makes sense to expose the SJCL instance so we don't
// have to include it twice.
exports.sjcl = require('./utils').sjcl;
exports.Wallet = require('ripple-wallet-generator')({sjcl: exports.sjcl});
exports.types = require('./serializedtypes');
// camelCase to under_scored API conversion
function attachUnderscored(name) {
const o = exports[name];
Object.keys(o.prototype).forEach(function(key) {
const UPPERCASE = /([A-Z]{1})[a-z]+/g;
if (!UPPERCASE.test(key)) {
return;
}
const underscored = key.replace(UPPERCASE, function(c) {
return '_' + c.toLowerCase();
});
o.prototype[underscored] = o.prototype[key];
});
}
['Remote',
'Request',
'Transaction',
'Account',
'Server'
].forEach(attachUnderscored);
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,102 +0,0 @@
'use strict';
/*eslint new-cap: 1*/
var sjcl = require('./utils').sjcl;
var UInt160 = require('./uint160').UInt160;
var UInt256 = require('./uint256').UInt256;
var Base = require('./base').Base;
function KeyPair() {
this._curve = sjcl.ecc.curves.k256;
this._secret = null;
this._pubkey = null;
}
KeyPair.from_bn_secret = function(j) {
return (j instanceof this) ? j.clone() : (new this()).parse_bn_secret(j);
};
KeyPair.prototype.parse_bn_secret = function(j) {
this._secret = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves.k256, j);
return this;
};
/**
* @private
*
* @return {sjcl.ecc.ecdsa.publicKey} public key
*/
KeyPair.prototype._pub = function() {
var curve = this._curve;
if (!this._pubkey && this._secret) {
var exponent = this._secret._exponent;
this._pubkey = new sjcl.ecc.ecdsa.publicKey(curve, curve.G.mult(exponent));
}
return this._pubkey;
};
/**
* @private
*
* @return {sjcl.bitArray} public key bits in compressed form
*/
KeyPair.prototype._pub_bits = function() {
var pub = this._pub();
if (!pub) {
return null;
}
var point = pub._point, y_even = point.y.mod(2).equals(0);
return sjcl.bitArray.concat(
[sjcl.bitArray.partial(8, y_even ? 0x02 : 0x03)],
point.x.toBits(this._curve.r.bitLength())
);
};
/**
* @return {String} public key bytes in compressed form, hex encoded.
*/
KeyPair.prototype.to_hex_pub = function() {
var bits = this._pub_bits();
if (!bits) {
return null;
}
return sjcl.codec.hex.fromBits(bits).toUpperCase();
};
function sha256_ripemd160(bits) {
return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
}
KeyPair.prototype.get_address = function() {
var bits = this._pub_bits();
if (!bits) {
return null;
}
var hash = sha256_ripemd160(bits);
var address = UInt160.from_bits(hash);
address.set_version(Base.VER_ACCOUNT_ID);
return address;
};
KeyPair.prototype.sign = function(hash) {
var PARANOIA_256_BITS = 6; // sjcl constant for ensuring 256 bits of entropy
hash = UInt256.from_json(hash);
var sig = this._secret.sign(hash.to_bits(), PARANOIA_256_BITS);
sig = this._secret.canonicalizeSignature(sig);
return this._secret.encodeDER(sig);
};
exports.KeyPair = KeyPair;

View File

@@ -1,19 +1,19 @@
/* eslint-disable valid-jsdoc */
'use strict';
var Transaction = require('./transaction').Transaction;
var SHAMap = require('./shamap').SHAMap;
var SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
var SerializedObject = require('./serializedobject').SerializedObject;
var stypes = require('./serializedtypes');
var UInt160 = require('./uint160').UInt160;
var Currency = require('./currency').Currency;
const BigNumber = require('bignumber.js');
const Transaction = require('./transaction').Transaction;
const SHAMap = require('./shamap').SHAMap;
const SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
const SerializedObject = require('./serializedobject').SerializedObject;
const stypes = require('./serializedtypes');
const UInt160 = require('./uint160').UInt160;
const Currency = require('./currency').Currency;
function Ledger() {
this.ledger_json = {};
}
Ledger.from_json = function(v) {
var ledger = new Ledger();
const ledger = new Ledger();
ledger.parse_json(v);
return ledger;
};
@@ -23,14 +23,13 @@ Ledger.space = require('./ledgerspaces');
/**
* Generate the key for an AccountRoot entry.
*
* @param {String|UInt160} account Ripple Account
* @param {String|UInt160} accountArg - Ripple Account
* @return {UInt256}
*/
Ledger.calcAccountRootEntryHash =
Ledger.prototype.calcAccountRootEntryHash = function(account) {
account = UInt160.from_json(account);
var index = new SerializedObject();
Ledger.prototype.calcAccountRootEntryHash = function(accountArg) {
const account = UInt160.from_json(accountArg);
const index = new SerializedObject();
index.append([0, Ledger.space.account.charCodeAt(0)]);
index.append(account.to_bytes());
@@ -41,17 +40,15 @@ Ledger.prototype.calcAccountRootEntryHash = function(account) {
/**
* Generate the key for an Offer entry.
*
* @param {String|UInt160} account Ripple Account
* @param {Number} sequence Sequence number of the OfferCreate transaction
* @param {String|UInt160} accountArg - Ripple Account
* @param {Number} sequence - Sequence number of the OfferCreate transaction
* that instantiated this offer.
* @return {UInt256}
*/
Ledger.calcOfferEntryHash =
Ledger.prototype.calcOfferEntryHash = function(account, sequence) {
account = UInt160.from_json(account);
sequence = parseInt(sequence, 10);
var index = new SerializedObject();
Ledger.prototype.calcOfferEntryHash = function(accountArg, sequence) {
const account = UInt160.from_json(accountArg);
const index = new SerializedObject();
index.append([0, Ledger.space.offer.charCodeAt(0)]);
index.append(account.to_bytes());
@@ -65,17 +62,17 @@ Ledger.prototype.calcOfferEntryHash = function(account, sequence) {
*
* The ordering of the two account parameters does not matter.
*
* @param {String|UInt160} account1 First Ripple Account
* @param {String|UInt160} account2 Second Ripple Account
* @param {String|Currency} currency The currency code
* @param {String|UInt160} _account1 - First Ripple Account
* @param {String|UInt160} _account2 - Second Ripple Account
* @param {String|Currency} _currency - The currency code
* @return {UInt256}
*/
Ledger.calcRippleStateEntryHash =
Ledger.prototype.calcRippleStateEntryHash = function(
account1, account2, currency) {
currency = Currency.from_json(currency);
account1 = UInt160.from_json(account1);
account2 = UInt160.from_json(account2);
_account1, _account2, _currency) {
const currency = Currency.from_json(_currency);
const account1 = UInt160.from_json(_account1);
const account2 = UInt160.from_json(_account2);
if (!account1.is_valid()) {
throw new Error('Invalid first account');
@@ -87,18 +84,14 @@ Ledger.prototype.calcRippleStateEntryHash = function(
throw new Error('Invalid currency');
}
// The lower ID has to come first
if (account1.to_bn().greaterEquals(account2.to_bn())) {
var tmp = account2;
account2 = account1;
account1 = tmp;
}
var index = new SerializedObject();
const swap = account1.greater_than(account2);
const lowAccount = swap ? account2 : account1;
const highAccount = swap ? account1 : account2;
const index = new SerializedObject();
index.append([0, Ledger.space.rippleState.charCodeAt(0)]);
index.append(account1.to_bytes());
index.append(account2.to_bytes());
index.append(lowAccount.to_bytes());
index.append(highAccount.to_bytes());
index.append(currency.to_bytes());
return index.hash();
@@ -109,13 +102,13 @@ Ledger.prototype.parse_json = function(v) {
};
Ledger.prototype.calc_tx_hash = function() {
var tx_map = new SHAMap();
const tx_map = new SHAMap();
this.ledger_json.transactions.forEach(function(tx_json) {
var tx = Transaction.from_json(tx_json);
var meta = SerializedObject.from_json(tx_json.metaData);
const tx = Transaction.from_json(tx_json);
const meta = SerializedObject.from_json(tx_json.metaData);
var data = new SerializedObject();
const data = new SerializedObject();
stypes.VariableLength.serialize(data, tx.serialize().to_hex());
stypes.VariableLength.serialize(data, meta.to_hex());
tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
@@ -125,22 +118,23 @@ Ledger.prototype.calc_tx_hash = function() {
};
/**
* @param options .sanity_test {Boolean}
* @return hash of shamap
* @param {Object} options - object
*
* If `true`, will serialize each accountState item to binary and then back to
* json before finally serializing for hashing. This is mostly to expose any
* issues with ripple-lib's binary <--> json codecs.
* @param {Boolean} [options.sanity_test=false] - If `true`, will serialize each
* accountState item to binary and then back to json before finally
* serializing for hashing. This is mostly to expose any issues with
* ripple-lib's binary <--> json codecs.
*
* @return {UInt256} - hash of shamap
*/
Ledger.prototype.calc_account_hash = function(options) {
var account_map = new SHAMap();
var erred;
const account_map = new SHAMap();
let erred;
this.ledger_json.accountState.forEach(function(le) {
var data = SerializedObject.from_json(le);
let data = SerializedObject.from_json(le);
var json;
let json;
if (options && options.sanity_test) {
try {
json = data.to_json();
@@ -163,4 +157,24 @@ Ledger.prototype.calc_account_hash = function(options) {
return account_map.hash();
};
// see rippled Ledger::updateHash()
Ledger.calculateLedgerHash =
Ledger.prototype.calculateLedgerHash = function(ledgerHeader) {
const so = new SerializedObject();
const prefix = 0x4C575200;
const totalCoins = (new BigNumber(ledgerHeader.total_coins)).toString(16);
stypes.Int32.serialize(so, Number(ledgerHeader.ledger_index));
stypes.Int64.serialize(so, totalCoins);
stypes.Hash256.serialize(so, ledgerHeader.parent_hash);
stypes.Hash256.serialize(so, ledgerHeader.transaction_hash);
stypes.Hash256.serialize(so, ledgerHeader.account_hash);
stypes.Int32.serialize(so, ledgerHeader.parent_close_time);
stypes.Int32.serialize(so, ledgerHeader.close_time);
stypes.Int8.serialize(so, ledgerHeader.close_time_resolution);
stypes.Int8.serialize(so, ledgerHeader.close_flags);
return so.hash(prefix).to_hex();
};
exports.Ledger = Ledger;

View File

@@ -62,9 +62,9 @@ function OrderBook(remote,
this._ownerFunds = {};
this._ownerOffersTotal = {};
// We consider ourselves synchronized if we have a current
// We consider ourselves synced if we have a current
// copy of the offers, we are online and subscribed to updates
this._synchronized = false;
this._synced = false;
// Transfer rate of the taker gets currency issuer
this._issuerTransferRate = null;
@@ -278,7 +278,6 @@ OrderBook.prototype.requestOffers = function(callback=function() {}) {
}
self.setOffers(res.offers);
self._synchronized = true;
self.notifyDirectOffersChanged();
callback(null, self._offers);
@@ -293,7 +292,8 @@ OrderBook.prototype.requestOffers = function(callback=function() {}) {
callback(err);
}
const request = this._remote.requestBookOffers(this.toJSON());
const requestOptions = _.merge({}, this.toJSON(), {ledger: 'validated'});
const request = this._remote.requestBookOffers(requestOptions);
request.once('success', handleOffers);
request.once('error', handleError);
request.request();
@@ -408,7 +408,7 @@ OrderBook.prototype.resetCache = function() {
this._ownerFunds = {};
this._ownerOffersTotal = {};
this._offerCounts = {};
this._synchronized = false;
this._synced = false;
};
/**
@@ -754,9 +754,15 @@ OrderBook.prototype.updateFundedAmounts = function(transaction) {
log.info('waiting for transfer rate');
}
this.requestTransferRate(function() {
// Defer until transfer rate is requested
self.updateFundedAmounts(transaction);
this.requestTransferRate(function(err) {
if (err) {
log.error(
'Failed to request transfer rate, will not update funded amounts: '
+ err.toString());
} else {
// Defer until transfer rate is requested
self.updateFundedAmounts(transaction);
}
});
return;
}
@@ -842,7 +848,7 @@ OrderBook.prototype.updateOwnerOffersFundedAmount = function(account) {
OrderBook.prototype.notify = function(transaction) {
const self = this;
if (!this._subscribed) {
if (!(this._subscribed && this._synced)) {
return;
}
@@ -1083,6 +1089,7 @@ OrderBook.prototype.setOffers = function(offers) {
});
this._offers = newOffers;
this._synced = true;
};
/**
@@ -1101,7 +1108,7 @@ OrderBook.prototype.offers =
OrderBook.prototype.getOffers = function(callback) {
assert.strictEqual(typeof callback, 'function', 'Callback missing');
if (this._synchronized) {
if (this._synced) {
callback(null, this._offers);
} else {
this.once('model', function(m) {

View File

@@ -1,7 +1,7 @@
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var Amount = require('./amount').Amount;
var extend = require('extend');
'use strict';
const EventEmitter = require('events').EventEmitter;
const util = require('util');
const Amount = require('./amount').Amount;
/**
* Represents a persistent path finding request.
@@ -10,16 +10,18 @@ var extend = require('extend');
* find request is triggered it will supercede the existing one, making it emit
* the 'end' and 'superceded' events.
*/
function PathFind(remote, src_account, dst_account, dst_amount, src_currencies) {
function PathFind(remote, src_account, dst_account, dst_amount, src_currencies
) {
EventEmitter.call(this);
this.remote = remote;
this.src_account = src_account;
this.dst_account = dst_account;
this.dst_amount = dst_amount;
this.src_account = src_account;
this.dst_account = dst_account;
this.dst_amount = dst_amount;
this.src_currencies = src_currencies;
};
}
util.inherits(PathFind, EventEmitter);
@@ -32,14 +34,16 @@ util.inherits(PathFind, EventEmitter);
* so you should only have to call it if the path find was closed or superceded
* and you wish to restart it.
*/
PathFind.prototype.create = function () {
var self = this;
var req = this.remote.request_path_find_create(
this.src_account,
this.dst_account,
this.dst_amount,
this.src_currencies);
PathFind.prototype.create = function() {
const self = this;
const req = this.remote.requestPathFindCreate({
source_account: this.src_account,
destination_account: this.dst_account,
destination_amount: this.dst_amount,
source_currencies: this.src_currencies
});
req.once('error', function(err) {
self.emit('error', err);
@@ -54,27 +58,28 @@ PathFind.prototype.create = function () {
req.broadcast().request();
};
PathFind.prototype.close = function () {
this.remote.request_path_find_close().broadcast().request();
PathFind.prototype.close = function() {
this.removeAllListeners('update');
this.remote.requestPathFindClose().broadcast().request();
this.emit('end');
this.emit('close');
};
PathFind.prototype.notify_update = function (message) {
var src_account = message.source_account;
var dst_account = message.destination_account;
var dst_amount = Amount.from_json(message.destination_amount);
PathFind.prototype.notify_update = function(message) {
const src_account = message.source_account;
const dst_account = message.destination_account;
const dst_amount = Amount.from_json(message.destination_amount);
// Only pass the event along if this path find response matches what we were
// looking for.
if (this.src_account === src_account &&
this.dst_account === dst_account &&
this.dst_amount.equals(dst_amount)) {
dst_amount.equals(this.dst_amount)) {
this.emit('update', message);
}
};
PathFind.prototype.notify_superceded = function () {
PathFind.prototype.notify_superceded = function() {
// XXX If we're set to re-subscribe whenever we connect to a new server, then
// we should cancel that behavior here. See PathFind#create.

View File

@@ -45,7 +45,7 @@ class RangeSet {
const rangeStrings = rangesString.split(',');
_.forEach(rangeStrings, rangeString => {
const range = rangeString.split('-').map(Number);
this.addRange(range[0], range[1]);
this.addRange(range[0], range.length === 1 ? range[0] : range[1]);
});
}

View File

@@ -65,6 +65,7 @@ function Remote(options = {}) {
this._transaction_listeners = 0;
this._received_tx = new LRU({max: 100});
this._cur_path_find = null;
this._queued_path_finds = [];
if (this.local_signing) {
// Local signing implies local fees and sequences
@@ -90,7 +91,7 @@ function Remote(options = {}) {
this._books = { };
// Secrets that we know about.
// Secrets can be set by calling set_secret(account, secret).
// Secrets can be set by calling setSecret(account, secret).
// account : secret
this.secrets = { };
@@ -205,7 +206,7 @@ Remote.TRANSACTION_EVENTS = [
'transaction_all'
];
// Flags for ledger entries. In support of account_root().
// Flags for ledger entries. In support of accountRoot().
Remote.flags = {
// AccountRoot
account_root: {
@@ -469,7 +470,7 @@ Remote.prototype.disconnect = function(callback_) {
server.disconnect();
});
this._set_state('offline');
this._setState('offline');
return this;
};
@@ -744,20 +745,18 @@ Remote.prototype.getServer = function() {
* @param {Request} request
*/
Remote.prototype.request = function(request_) {
let request = request_;
Remote.prototype.request = function(request) {
if (typeof request === 'string') {
if (!/^request_/.test(request)) {
request = 'request_' + request;
}
const prefix = /^request_/.test(request) ? '' : 'request_';
const requestName = prefix + request;
const methodName = requestName.replace(/(\_\w)/g, m => m[1].toUpperCase());
if (typeof this[request] === 'function') {
if (typeof this[methodName] === 'function') {
const args = _.slice(arguments, 1);
return this[request].apply(this, args);
return this[methodName].apply(this, args);
}
throw new Error('Command does not exist: ' + request);
throw new Error('Command does not exist: ' + requestName);
}
if (!(request instanceof Request)) {
@@ -1748,8 +1747,8 @@ Remote.prototype._serverPrepareSubscribe = function(server, callback_) {
const request = this.requestSubscribe(feeds);
function serverSubscribed(message) {
self._stand_alone = !!message.stand_alone;
self._testnet = !!message.testnet;
self._stand_alone = Boolean(message.stand_alone);
self._testnet = Boolean(message.testnet);
self._handleLedgerClosed(message, server);
self.emit('subscribed');
}
@@ -1936,9 +1935,14 @@ Remote.prototype.findAccount = function(accountID) {
* @return {PathFind}
*/
function createPathFind(options_) {
function createPathFind(options_, callback) {
const options = {};
if (this._cur_path_find !== null) {
this._queued_path_finds.push({options: options_, callback: callback});
return null;
}
if (_.isPlainObject(options_)) {
_.merge(options, options_);
} else {
@@ -1957,10 +1961,21 @@ function createPathFind(options_) {
this._cur_path_find.notify_superceded();
}
pathFind.create();
if (callback) {
pathFind.on('update', (data) => {
if (data.full_reply) {
pathFind.close();
callback(null, data);
}
});
pathFind.on('error', (error) => {
pathFind.close();
callback(error);
});
}
this._cur_path_find = pathFind;
pathFind.create();
return pathFind;
}
@@ -2279,7 +2294,7 @@ Remote.prototype.requestRipplePathFind = function(options_, callback_) {
destination_account: options_.dst_account,
destination_amount: options_.dst_amount,
source_currencies: options_.src_currencies
}, options_);
}, options_);
} else {
_.merge(options, makeOptions(
'ripple_path_find',
@@ -2327,7 +2342,7 @@ Remote.prototype.requestPathFindCreate = function(options_, callback_) {
destination_account: options_.dst_account,
destination_amount: options_.dst_amount,
source_currencies: options_.src_currencies
}, options_);
}, options_);
} else {
_.merge(options, makeOptions(
'path_find',
@@ -2371,6 +2386,11 @@ Remote.prototype.requestPathFindClose = function(callback) {
request.message.subcommand = 'close';
request.callback(callback);
this._cur_path_find = null;
if (this._queued_path_finds.length > 0) {
const pathfind = this._queued_path_finds.shift();
this.createPathFind(pathfind.options, pathfind.callback);
}
return request;
};
@@ -2486,7 +2506,7 @@ Remote.prototype.createTransaction = function(type, options = {}) {
TrustSet: transaction.trustSet,
OfferCreate: transaction.offerCreate,
OfferCancel: transaction.offerCancel,
SetRegularkey: transaction.setRegularKey
SetRegularKey: transaction.setRegularKey
};
const transactionConstructor = constructorMap[type];

View File

@@ -4,18 +4,18 @@
// Seed support
//
const {KeyPair, KeyType} = require('ripple-keypairs');
const {decodeSeed, encodeSeed} = require('ripple-address-codec');
const extend = require('extend');
const utils = require('./utils');
const sjcl = utils.sjcl;
const sjclcodec = require('sjcl-codec');
const BN = require('bn.js');
const hashjs = require('hash.js');
const Base = require('./base').Base;
const UInt = require('./uint').UInt;
const UInt160 = require('./uint160').UInt160;
const KeyPair = require('./keypair').KeyPair;
const Seed = extend(function() {
this._curve = sjcl.ecc.curves.k256;
this._value = NaN;
this._type = KeyType.secp256k1;
}, UInt);
Seed.width = 16;
@@ -28,14 +28,15 @@ Seed.prototype.parse_json = function(j) {
if (typeof j === 'string') {
if (!j.length) {
this._value = NaN;
// XXX Should actually always try and continue if it failed.
} else if (j[0] === 's') {
this._value = Base.decode_check(Base.VER_FAMILY_SEED, j);
} else if (/^[0-9a-fA-f]{32}$/.test(j)) {
this.parse_hex(j);
// XXX Should also try 1751
} else {
this.parse_passphrase(j);
this.parse_base58(j);
if (!this.is_valid()) {
this.parse_hex(j);
// XXX Should also try 1751
}
if (!this.is_valid() && j[0] !== 's') {
this.parse_passphrase(j);
}
}
} else {
this._value = NaN;
@@ -44,15 +45,37 @@ Seed.prototype.parse_json = function(j) {
return this;
};
Seed.prototype.parse_base58 = function(j) {
if (typeof j !== 'string') {
throw new Error('Value must be a string');
}
if (!j.length || j[0] !== 's') {
this._value = NaN;
} else {
try {
const {bytes, type} = decodeSeed(j);
this._value = new BN(bytes);
this._type = type;
} catch (e) {
this._value = NaN;
}
}
return this;
};
Seed.prototype.set_ed25519 = function() {
this._type = KeyType.ed25519;
return this;
};
Seed.prototype.parse_passphrase = function(j) {
if (typeof j !== 'string') {
throw new Error('Passphrase must be a string');
}
const hash = sjcl.hash.sha512.hash(sjcl.codec.utf8String.toBits(j));
const bits = sjcl.bitArray.bitSlice(hash, 0, 128);
this.parse_bits(bits);
const phraseBytes = sjclcodec.bytes.fromBits(sjclcodec.utf8String.toBits(j));
const hash = hashjs.sha512().update(phraseBytes).digest();
this.parse_bytes(hash.slice(0, 16));
return this;
};
@@ -61,100 +84,14 @@ Seed.prototype.to_json = function() {
if (!(this.is_valid())) {
return NaN;
}
const output = Base.encode_check(Base.VER_FAMILY_SEED, this.to_bytes());
return output;
return encodeSeed(this.to_bytes(), this._type);
};
function append_int(a, i) {
return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff);
}
function firstHalfOfSHA512(bytes) {
return sjcl.bitArray.bitSlice(
sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)),
0, 256
);
}
// Removed a `*` so this JSDoc-ish syntax is ignored.
// This will soon all change anyway.
/*
* @param account
* {undefined} take first, default, KeyPair
*
* {Number} specifies the account number of the KeyPair
* desired.
*
* {Uint160} (from_json able), specifies the address matching the KeyPair
* that is desired.
*
* @param maxLoops (optional)
* {Number} specifies the amount of attempts taken
* to generate a matching KeyPair
*
*/
Seed.prototype.get_key = function(account, maxLoops) {
let account_number = 0, address;
let max_loops = maxLoops || 1;
Seed.prototype.get_key = function() {
if (!this.is_valid()) {
throw new Error('Cannot generate keys from invalid seed!');
}
if (account) {
if (typeof account === 'number') {
account_number = account;
max_loops = account_number + 1;
} else {
address = UInt160.from_json(account);
}
}
let private_gen, public_gen;
const curve = this._curve;
let i = 0;
do {
private_gen = sjcl.bn.fromBits(
firstHalfOfSHA512(append_int(this.to_bytes(), i)));
i++;
} while (!curve.r.greaterEquals(private_gen));
public_gen = curve.G.mult(private_gen);
let sec;
let key_pair;
do {
i = 0;
do {
sec = sjcl.bn.fromBits(
firstHalfOfSHA512(
append_int(
append_int(public_gen.toBytesCompressed(), account_number)
,
i
)));
i++;
} while (!curve.r.greaterEquals(sec));
account_number++;
sec = sec.add(private_gen).mod(curve.r);
key_pair = KeyPair.from_bn_secret(sec);
if (max_loops-- <= 0) {
// We are almost certainly looking for an account that would take same
// value of $too_long {forever, ...}
throw new Error('Too many loops looking for KeyPair yielding ' +
address.to_json() + ' from ' + this.to_json());
}
} while (address && !key_pair.get_address().equals(address));
return key_pair;
return KeyPair.fromSeed(this.to_bytes(), this._type);
};
exports.Seed = Seed;

View File

@@ -1,15 +1,15 @@
'use strict';
const _ = require('lodash');
const assert = require('assert');
const extend = require('extend');
const BN = require('bn.js');
const hashjs = require('hash.js');
const sjclcodec = require('sjcl-codec');
const binformat = require('./binformat');
const stypes = require('./serializedtypes');
const utils = require('./utils');
const UInt256 = require('./uint256').UInt256;
const sjcl = utils.sjcl;
const TRANSACTION_TYPES = { };
Object.keys(binformat.tx).forEach(function(key) {
@@ -28,20 +28,16 @@ Object.keys(binformat.ter).forEach(function(key) {
TRANSACTION_RESULTS[binformat.ter[key]] = key;
});
function normalize_sjcl_bn_hex(string) {
const hex = string.slice(2); // remove '0x' prefix
// now strip leading zeros
const i = _.findIndex(hex, function(c) {
return c !== '0';
});
return i >= 0 ? hex.slice(i) : '0';
function fieldType(fieldName) {
const fieldDef = binformat.fieldsInverseMap[fieldName];
return binformat.types[fieldDef[0]];
}
function SerializedObject(buf) {
if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf))) {
this.buffer = buf;
} else if (typeof buf === 'string') {
this.buffer = sjcl.codec.bytes.fromBits(sjcl.codec.hex.toBits(buf));
this.buffer = sjclcodec.bytes.fromBits(sjclcodec.hex.toBits(buf));
} else if (!buf) {
this.buffer = [];
} else {
@@ -205,11 +201,11 @@ SerializedObject.prototype.read = readOrPeek(true);
SerializedObject.prototype.peek = readOrPeek(false);
SerializedObject.prototype.to_bits = function() {
return sjcl.codec.bytes.toBits(this.buffer);
return sjclcodec.bytes.toBits(this.buffer);
};
SerializedObject.prototype.to_hex = function() {
return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
return sjclcodec.hex.fromBits(this.to_bits()).toUpperCase();
};
SerializedObject.prototype.to_json = function() {
@@ -231,12 +227,12 @@ SerializedObject.prototype.to_json = function() {
return output;
};
SerializedObject.jsonify_structure = function(structure, field_name) {
SerializedObject.jsonify_structure = function(structure, fieldName) {
let output;
switch (typeof structure) {
case 'number':
switch (field_name) {
switch (fieldName) {
case 'LedgerEntryType':
output = LEDGER_ENTRY_TYPES[structure];
break;
@@ -257,11 +253,10 @@ SerializedObject.jsonify_structure = function(structure, field_name) {
if (typeof structure.to_json === 'function') {
output = structure.to_json();
} else if (structure instanceof sjcl.bn) {
output = ('0000000000000000' +
normalize_sjcl_bn_hex(structure.toString())
.toUpperCase()
).slice(-16);
} else if (structure instanceof BN) {
// We assume that any BN is a UInt64 field
assert.equal(fieldType(fieldName), 'Int64');
output = utils.arrayToHex(structure.toArray('bn', 8));
} else {
// new Array or Object
output = new structure.constructor();
@@ -307,11 +302,9 @@ SerializedObject.prototype.hash = function(prefix) {
// Copy buffer to temporary buffer
sign_buffer.append(this.buffer);
const bytes = hashjs.sha512().update(sign_buffer.buffer).digest();
const bits = sjcl.codec.bytes.toBits(sign_buffer.buffer);
const sha512hex = sjcl.codec.hex.fromBits(sjcl.hash.sha512.hash(bits));
return UInt256.from_hex(sha512hex.substr(0, 64).toUpperCase());
return UInt256.from_bytes(bytes.slice(0, 32));
};
// DEPRECATED

View File

@@ -10,13 +10,13 @@
const assert = require('assert');
const extend = require('extend');
const BN = require('bn.js');
const GlobalBigNumber = require('bignumber.js');
const sjclcodec = require('sjcl-codec');
const Amount = require('./amount').Amount;
const Currency = require('./currency').Currency;
const binformat = require('./binformat');
const utils = require('./utils');
const sjcl = utils.sjcl;
const SJCL_BN = sjcl.bn;
const UInt128 = require('./uint128').UInt128;
const UInt160 = require('./uint160').UInt160;
@@ -44,8 +44,7 @@ function isHexInt64String(val) {
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
}
function serializeBits(so, bits, noLength) {
const byteData = sjcl.codec.bytes.fromBits(bits);
function serializeBytes(so, byteData, noLength) {
if (!noLength) {
SerializedType.serialize_varint(so, byteData.length);
}
@@ -53,23 +52,12 @@ function serializeBits(so, bits, noLength) {
}
function serializeHex(so, hexData, noLength) {
serializeBits(so, sjcl.codec.hex.toBits(hexData), noLength);
}
/**
* parses bytes as hex
*
* @param {Array} byte_array bytes
* @return {String} hex string
*/
function convertByteArrayToHex(byte_array) {
return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array))
.toUpperCase();
serializeBytes(so, utils.hexToArray(hexData), noLength);
}
function convertHexToString(hexString) {
const bits = sjcl.codec.hex.toBits(hexString);
return sjcl.codec.utf8String.fromBits(bits);
const bits = sjclcodec.hex.toBits(hexString);
return sjclcodec.utf8String.fromBits(bits);
}
function sort_fields(keys) {
@@ -308,25 +296,27 @@ const STInt64 = exports.Int64 = new SerializedType({
if (value < 0) {
throw new Error('Negative value for unsigned Int64 is invalid.');
}
bigNumObject = new SJCL_BN(value, 10);
bigNumObject = new BN(value, 10);
} else if (isString(value)) {
if (!isHexInt64String(value)) {
throw new Error('Not a valid hex Int64.');
}
bigNumObject = new SJCL_BN(value, 16);
} else if (value instanceof SJCL_BN) {
if (!value.greaterEquals(0)) {
bigNumObject = new BN(value, 16);
} else if (value instanceof BN) {
if (value.cmpn(0) < 0) {
throw new Error('Negative value for unsigned Int64 is invalid.');
}
bigNumObject = value;
} else {
throw new Error('Invalid type for Int64');
throw new Error('Invalid type for Int64: ' + (typeof value) + ' value');
}
serializeBits(so, bigNumObject.toBits(64), true); // noLength = true
// `'be'` means big endian, and the following arg is the byte length, which
// it will pad with 0s to if not enough bytes, or throw if over
serializeBytes(so, bigNumObject.toArray('be', 8), /* noLength= */true);
},
parse: function(so) {
const bytes = so.read(8);
return SJCL_BN.fromBits(sjcl.codec.bytes.toBits(bytes));
return new BN(bytes);
}
});
@@ -338,7 +328,7 @@ const STHash128 = exports.Hash128 = new SerializedType({
if (!hash.is_valid()) {
throw new Error('Invalid Hash128');
}
serializeBits(so, hash.to_bits(), true); // noLength = true
serializeBytes(so, hash.to_bytes(), true); // noLength = true
},
parse: function(so) {
return UInt128.from_bytes(so.read(16));
@@ -353,7 +343,7 @@ const STHash256 = exports.Hash256 = new SerializedType({
if (!hash.is_valid()) {
throw new Error('Invalid Hash256');
}
serializeBits(so, hash.to_bits(), true); // noLength = true
serializeBytes(so, hash.to_bytes(), true); // noLength = true
},
parse: function(so) {
return UInt256.from_bytes(so.read(32));
@@ -368,7 +358,7 @@ const STHash160 = exports.Hash160 = new SerializedType({
if (!hash.is_valid()) {
throw new Error('Invalid Hash160');
}
serializeBits(so, hash.to_bits(), true); // noLength = true
serializeBytes(so, hash.to_bytes(), true); // noLength = true
},
parse: function(so) {
return UInt160.from_bytes(so.read(20));
@@ -442,7 +432,7 @@ exports.Quality = new SerializedType({
lo = parseInt(mantissaHex.slice(-8), 16);
}
const valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
const valueBytes = sjclcodec.bytes.fromBits([hi, lo]);
so.append(valueBytes);
}
@@ -482,7 +472,7 @@ const STAmount = exports.Amount = new SerializedType({
valueHex = '0' + valueHex;
}
valueBytes = sjcl.codec.bytes.fromBits(sjcl.codec.hex.toBits(valueHex));
valueBytes = sjclcodec.bytes.fromBits(sjclcodec.hex.toBits(valueHex));
// Clear most significant two bits - these bits should already be 0 if
// Amount enforces the range correctly, but we'll clear them anyway just
// so this code can make certain guarantees about the encoded value.
@@ -516,7 +506,7 @@ const STAmount = exports.Amount = new SerializedType({
lo = parseInt(mantissaHex.slice(-8), 16);
}
valueBytes = sjcl.codec.bytes.fromBits([hi, lo]);
valueBytes = sjclcodec.bytes.fromBits([hi, lo]);
}
so.append(valueBytes);
@@ -582,7 +572,7 @@ const STVL = exports.VariableLength = exports.VL = new SerializedType({
},
parse: function(so) {
const len = this.parse_varint(so);
return convertByteArrayToHex(so.read(len));
return utils.arrayToHex(so.read(len));
}
});
@@ -594,7 +584,7 @@ const STAccount = exports.Account = new SerializedType({
if (!account.is_valid()) {
throw new Error('Invalid account!');
}
serializeBits(so, account.to_bits());
serializeBytes(so, account.to_bytes());
},
parse: function(so) {
const len = this.parse_varint(so);
@@ -807,27 +797,27 @@ exports.STMemo = new SerializedType({
if (parsedType !== 'unformatted_memo') {
output.parsed_memo_type = parsedType;
}
/*eslint-disable no-empty*/
/* eslint-disable no-empty */
} catch (e) {
// empty
// we don't know what's in the binary, apparently it's not a UTF-8
// string
// this is fine, we won't add the parsed_memo_type field
}
/*eslint-enable no-empty*/
/* eslint-enable no-empty */
}
if (output.MemoFormat !== undefined) {
try {
output.parsed_memo_format = convertHexToString(output.MemoFormat);
/*eslint-disable no-empty*/
/* eslint-disable no-empty */
} catch (e) {
// empty
// we don't know what's in the binary, apparently it's not a UTF-8
// string
// this is fine, we won't add the parsed_memo_format field
}
/*eslint-enable no-empty*/
/* eslint-enable no-empty */
}
if (output.MemoData !== undefined) {
@@ -842,7 +832,7 @@ exports.STMemo = new SerializedType({
// otherwise see if we can parse text
output.parsed_memo_data = convertHexToString(output.MemoData);
}
/*eslint-disable no-empty*/
/* eslint-disable no-empty */
} catch(e) {
// empty
// we'll fail in case the content does not match what the MemoFormat
@@ -850,7 +840,7 @@ exports.STMemo = new SerializedType({
// this is fine, we won't add the parsed_memo_data, the user has to
// parse themselves
}
/*eslint-enable no-empty*/
/* eslint-enable no-empty */
}
so.read(1);

View File

@@ -1,12 +1,14 @@
'use strict';
const _ = require('lodash');
const assert = require('assert');
const util = require('util');
const url = require('url');
const HttpsProxyAgent = require('https-proxy-agent');
const LRU = require('lru-cache');
const EventEmitter = require('events').EventEmitter;
const RippleError = require('./').RippleError;
const RippleError = require('./rippleerror').RippleError;
const Amount = require('./amount').Amount;
const RangeSet = require('./rangeset').RangeSet;
const log = require('./log').internal.sub('server');
@@ -621,7 +623,7 @@ Server.prototype._handleLedgerClosed = function(message) {
Server.prototype._handleServerStatus = function(message) {
// This message is only received when online.
// As we are connected, it is the definitive final state.
const isOnline = ~Server.onlineStates.indexOf(message.server_status);
const isOnline = _.includes(Server.onlineStates, message.server_status);
this._setState(isOnline ? 'online' : 'offline');
@@ -732,7 +734,7 @@ Server.prototype._handleResponseSubscribe = function(message) {
this._ledgerRanges.parseAndAddRanges(message.validated_ledgers);
}
if (~Server.onlineStates.indexOf(message.server_status)) {
if (_.includes(Server.onlineStates, message.server_status)) {
this._setState('online');
}
};
@@ -905,6 +907,9 @@ Server.prototype._feeTxUnit = function() {
*/
Server.prototype._reserve = function(ownerCount) {
// We should be in a valid state before calling this method
assert(this._reserve_base && this._reserve_inc);
const reserve_base = Amount.from_json(String(this._reserve_base));
const reserve_inc = Amount.from_json(String(this._reserve_inc));
const owner_count = ownerCount || 0;
@@ -913,7 +918,7 @@ Server.prototype._reserve = function(ownerCount) {
throw new Error('Owner count must not be negative.');
}
return reserve_base.add(reserve_inc.product_human(owner_count));
return reserve_base.add(reserve_inc.multiply(owner_count));
};
/**

View File

@@ -4,7 +4,7 @@ const util = require('util');
const lodash = require('lodash');
const EventEmitter = require('events').EventEmitter;
const utils = require('./utils');
const sjcl = require('./utils').sjcl;
const sjclcodec = require('sjcl-codec');
const Amount = require('./amount').Amount;
const Currency = require('./amount').Currency;
const UInt160 = require('./amount').UInt160;
@@ -399,8 +399,8 @@ Transaction.prototype.complete = function() {
if (typeof this.tx_json.SigningPubKey === 'undefined') {
try {
const seed = Seed.from_json(this._secret);
const key = seed.get_key(this.tx_json.Account);
this.tx_json.SigningPubKey = key.to_hex_pub();
const key = seed.get_key();
this.tx_json.SigningPubKey = key.pubKeyHex();
} catch(e) {
this.emit('error', new RippleError(
'tejSecretInvalid', 'Invalid secret'));
@@ -469,13 +469,13 @@ Transaction.prototype.hash = function(prefix_, asUINT256, serialized) {
return asUINT256 ? hash : hash.to_hex();
};
Transaction.prototype.sign = function(testnet) {
Transaction.prototype.sign = function() {
const seed = Seed.from_json(this._secret);
const prev_sig = this.tx_json.TxnSignature;
delete this.tx_json.TxnSignature;
const hash = this.signingHash(testnet);
const hash = this.signingHash();
// If the hash is the same, we can re-use the previous signature
if (prev_sig && hash === this.previousSigningHash) {
@@ -483,10 +483,8 @@ Transaction.prototype.sign = function(testnet) {
return this;
}
const key = seed.get_key(this.tx_json.Account);
const sig = key.sign(hash);
const hex = sjcl.codec.hex.fromBits(sig).toUpperCase();
const key = seed.get_key();
const hex = key.signHex(this.signingData().buffer);
this.tx_json.TxnSignature = hex;
this.previousSigningHash = hash;
@@ -781,8 +779,8 @@ Transaction.prototype.addMemo = function(options_) {
}
function convertStringToHex(string) {
const utf8String = sjcl.codec.utf8String.toBits(string);
return sjcl.codec.hex.fromBits(utf8String).toUpperCase();
const utf8String = sjclcodec.utf8String.toBits(string);
return sjclcodec.hex.fromBits(utf8String).toUpperCase();
}
const memo = {};
@@ -892,10 +890,6 @@ Transaction.prototype.accountSet = function(options_) {
};
Transaction.prototype.setAccountSetFlag = function(name, value) {
// if (this.getType() !== 'AccountSet') {
// throw new Error('TransactionType must be AccountSet to use ' + name);
// }
const accountSetFlags = Transaction.set_clear_flags.AccountSet;
let flagValue = value;
@@ -930,12 +924,6 @@ Transaction.prototype.setClearFlag = function(flag) {
Transaction.prototype.setTransferRate =
Transaction.prototype.transferRate = function(rate) {
/* eslint-disable max-len */
// if (this.getType() !== 'AccountSet') {
// throw new Error('TransactionType must be AccountSet to use TransferRate');
// }
/* eslint-enable max-len */
const transferRate = rate;
if (transferRate === 0) {
@@ -1037,26 +1025,14 @@ Transaction.prototype.rippleLineSet = function(options_) {
};
Transaction.prototype.setLimit = function(amount) {
// if (this.getType() !== 'TrustSet') {
// throw new Error('TransactionType must be TrustSet to use LimitAmount');
// }
return this._setAmount('LimitAmount', amount, {no_native: true});
};
Transaction.prototype.setQualityIn = function(quality) {
// if (this.getType() !== 'TrustSet') {
// throw new Error('TransactionType must be TrustSet to use QualityIn');
// }
return this._setUInt32('QualityIn', quality);
};
Transaction.prototype.setQualityOut = function(quality) {
// if (this.getType() !== 'TrustSet') {
// throw new Error('TransactionType must be TrustSet to use QualityOut');
// }
return this._setUInt32('QualityOut', quality);
};
@@ -1106,18 +1082,10 @@ Transaction.prototype.payment = function(options_) {
};
Transaction.prototype.setAmount = function(amount) {
// if (this.getType() !== 'Payment') {
// throw new Error('TransactionType must be Payment to use SendMax');
// }
return this._setAmount('Amount', amount);
};
Transaction.prototype.setDestination = function(destination) {
// if (this.getType() !== 'Payment') {
// throw new Error('TransactionType must be Payment to use Destination');
// }
return this._setAccount('Destination', destination);
};
@@ -1129,13 +1097,19 @@ Transaction.prototype.setDestination = function(destination) {
Transaction.prototype.setSendMax =
Transaction.prototype.sendMax = function(send_max) {
// if (this.getType() !== 'Payment') {
// throw new Error('TransactionType must be Payment to use SendMax');
// }
return this._setAmount('SendMax', send_max);
};
/**
* Set DeliverMin for Payment
*
* @param {String|Object} deliver_min minimum amount to deliver
*/
Transaction.prototype.setDeliverMin = function(deliver_min) {
return this._setAmount('DeliverMin', deliver_min);
};
/**
* Filter invalid properties from path objects in a path array
*
@@ -1186,9 +1160,6 @@ Transaction.prototype.pathAdd = function(path) {
if (!Array.isArray(path)) {
throw new Error('Path must be an array');
}
// if (this.getType() !== 'Payment') {
// throw new Error('TransactionType must be Payment to use Paths');
// }
this.tx_json.Paths = this.tx_json.Paths || [];
this.tx_json.Paths.push(Transaction._rewritePath(path));
@@ -1207,9 +1178,10 @@ Transaction.prototype.paths = function(paths) {
if (!Array.isArray(paths)) {
throw new Error('Paths must be an array');
}
// if (this.getType() !== 'Payment') {
// throw new Error('TransactionType must be Payment to use Paths');
// }
if (paths.length === 0) {
return this;
}
this.tx_json.Paths = [];
paths.forEach(this.addPath, this);
@@ -1228,10 +1200,6 @@ Transaction.prototype.paths = function(paths) {
Transaction.prototype.setBuildPath =
Transaction.prototype.buildPath = function(build) {
// if (this.getType() !== 'Payment') {
// throw new Error('TransactionType must be Payment to use build_path');
// }
this._build_path = build === undefined || build;
return this;
@@ -1245,10 +1213,6 @@ Transaction.prototype.buildPath = function(build) {
Transaction.prototype.setDestinationTag =
Transaction.prototype.destinationTag = function(tag) {
// if (this.getType() !== 'Payment') {
// throw new Error('TransactionType must be Payment to use DestinationTag');
// }
return this._setUInt32('DestinationTag', tag);
};
@@ -1260,10 +1224,6 @@ Transaction.prototype.destinationTag = function(tag) {
Transaction.prototype.setInvoiceID =
Transaction.prototype.invoiceID = function(id) {
// if (this.getType() !== 'Payment') {
// throw new Error('TransactionType must be Payment to use InvoiceID');
// }
return this._setHash256('InvoiceID', id, {pad: true});
};
@@ -1321,26 +1281,14 @@ Transaction.prototype.offerCreate = function(options_) {
};
Transaction.prototype.setTakerGets = function(amount) {
// if (this.getType() !== 'OfferCreate') {
// throw new Error('TransactionType must be OfferCreate to use TakerGets');
// }
return this._setAmount('TakerGets', amount);
};
Transaction.prototype.setTakerPays = function(amount) {
// if (this.getType() !== 'OfferCreate') {
// throw new Error('TransactionType must be OfferCreate to use TakerPays');
// }
return this._setAmount('TakerPays', amount);
};
Transaction.prototype.setExpiration = function(expiration) {
// if (this.getType() !== 'OfferCreate') {
// throw new Error('TransactionType must be OfferCreate to use Expiration');
// }
const timeOffset = expiration instanceof Date
? expiration.getTime()
: expiration;
@@ -1349,14 +1297,6 @@ Transaction.prototype.setExpiration = function(expiration) {
};
Transaction.prototype.setOfferSequence = function(offerSequence) {
/* eslint-disable max-len */
// if (!/^Offer(Cancel|Create)$/.test(this.getType())) {
// throw new Error(
// 'TransactionType must be OfferCreate or OfferCancel to use OfferSequence'
// );
// }
/* eslint-enable max-len */
return this._setUInt32('OfferSequence', offerSequence);
};
@@ -1423,13 +1363,6 @@ Transaction.prototype.submit = function(callback) {
return this;
}
/* eslint-disable max-len */
// if (this.state !== 'unsubmitted') {
// this.emit('error', new Error('Attempt to submit transaction more than once'));
// return;
// }
/* eslint-enable max-len */
this.getManager().submit(this);
return this;

View File

@@ -524,7 +524,7 @@ TransactionManager.prototype._prepareRequest = function(tx) {
tx.sign();
const serialized = tx.serialize();
submitRequest.tx_blob(serialized.to_hex());
submitRequest.txBlob(serialized.to_hex());
const hash = tx.hash(null, null, serialized);
tx.addId(hash);
@@ -534,9 +534,9 @@ TransactionManager.prototype._prepareRequest = function(tx) {
// sealed and delivered, and the txn unmodified.
// TODO: perhaps an exception should be raised if build_path is attempted
// while local signing
submitRequest.build_path(tx._build_path);
submitRequest.buildPath(tx._build_path);
submitRequest.secret(tx._secret);
submitRequest.tx_json(tx.tx_json);
submitRequest.txJson(tx.tx_json);
}
return submitRequest;
@@ -728,8 +728,7 @@ TransactionManager.prototype.submit = function(tx) {
if (typeof tx.tx_json.Sequence !== 'number') {
// Honor manually-set sequences
this._nextSequence += 1;
tx.tx_json.Sequence = this._nextSequence;
tx.tx_json.Sequence = this._nextSequence++;
}
tx.once('cleanup', function() {

View File

@@ -1,11 +1,12 @@
'use strict';
/*eslint new-cap: 1*/
/* eslint new-cap: 1 */
const assert = require('assert');
const lodash = require('lodash');
const sjclcodec = require('sjcl-codec');
const utils = require('./utils');
const sjcl = utils.sjcl;
const BN = require('bn.js');
//
// Abstract UInt class
@@ -14,7 +15,7 @@ const sjcl = utils.sjcl;
//
function UInt() {
// Internal form: NaN or sjcl.bn
// Internal form: NaN or BN
this._value = NaN;
}
@@ -67,15 +68,6 @@ UInt.from_bytes = function(j) {
return (new this()).parse_bytes(j);
};
// Return a new UInt from j.
UInt.from_bn = function(j) {
if (j instanceof this) {
return j.clone();
}
return (new this()).parse_bn(j);
};
// Return a new UInt from j.
UInt.from_number = function(j) {
if (j instanceof this) {
@@ -108,16 +100,33 @@ UInt.prototype.copyTo = function(d) {
return d;
};
UInt.prototype.equals = function(d) {
return this.is_valid() && d.is_valid() && this._value.equals(d._value);
UInt.prototype.equals = function(o) {
return this.is_valid() &&
o.is_valid() &&
// This throws but the expression will short circuit
this.cmp(o) === 0;
};
UInt.prototype.cmp = function(o) {
assert(this.is_valid() && o.is_valid());
return this._value.cmp(o._value);
};
UInt.prototype.greater_than = function(o) {
return this.cmp(o) > 0;
};
UInt.prototype.less_than = function(o) {
return this.cmp(o) < 0;
};
UInt.prototype.is_valid = function() {
return this._value instanceof sjcl.bn;
return this._value instanceof BN;
};
UInt.prototype.is_zero = function() {
return this.is_valid() && this._value.equals(new sjcl.bn(0));
// cmpn means cmp with N)umber
return this.is_valid() && this._value.cmpn(0) === 0;
};
/**
@@ -150,14 +159,14 @@ UInt.prototype.parse_generic = function(j) {
case subclass.STR_ZERO:
case subclass.ACCOUNT_ZERO:
case subclass.HEX_ZERO:
this._value = new sjcl.bn(0);
this._value = new BN(0);
break;
case '1':
case subclass.STR_ONE:
case subclass.ACCOUNT_ONE:
case subclass.HEX_ONE:
this._value = new sjcl.bn(1);
this._value = new BN(1);
break;
default:
@@ -165,7 +174,7 @@ UInt.prototype.parse_generic = function(j) {
switch (j.length) {
case subclass.width:
const hex = utils.arrayToHex(utils.stringToArray(j));
this._value = new sjcl.bn(hex, 16);
this._value = new BN(hex, 16);
break;
case subclass.width * 2:
// Assume hex, check char set
@@ -187,7 +196,7 @@ UInt.prototype.parse_generic = function(j) {
UInt.prototype.parse_hex = function(j) {
if (new RegExp(`^[0-9A-Fa-f]{${this.constructor.width * 2}}$`).test(j)) {
this._value = new sjcl.bn(j, 16);
this._value = new BN(j, 16);
} else {
this._value = NaN;
}
@@ -198,24 +207,12 @@ UInt.prototype.parse_hex = function(j) {
};
UInt.prototype.parse_bits = function(j) {
if (sjcl.bitArray.bitLength(j) === this.constructor.width * 8) {
this._value = sjcl.bn.fromBits(j);
// let bytes = sjcl.codec.bytes.fromBits(j);
// this.parse_bytes(bytes);
} else {
this._value = NaN;
}
this._update();
return this;
return this.parse_bytes(sjclcodec.bytes.fromBits(j));
};
UInt.prototype.parse_bytes = function(j) {
if (Array.isArray(j) && j.length === this.constructor.width) {
const bits = sjcl.codec.bytes.toBits(j);
this._value = sjcl.bn.fromBits(bits);
this._value = new BN(j);
} else {
this._value = NaN;
}
@@ -225,26 +222,13 @@ UInt.prototype.parse_bytes = function(j) {
return this;
};
UInt.prototype.parse_json = UInt.prototype.parse_hex;
UInt.prototype.parse_bn = function(j) {
if ((j instanceof sjcl.bn) && j.bitLength() <= this.constructor.width * 8) {
this._value = new sjcl.bn(j);
} else {
this._value = NaN;
}
this._update();
return this;
};
UInt.prototype.parse_number = function(j) {
this._value = NaN;
if (typeof j === 'number' && isFinite(j) && j >= 0) {
this._value = new sjcl.bn(j);
this._value = new BN(j);
}
this._update();
@@ -258,7 +242,7 @@ UInt.prototype.to_bytes = function() {
return null;
}
return sjcl.codec.bytes.fromBits(this.to_bits());
return this._value.toArray('be', this.constructor.width);
};
UInt.prototype.to_hex = function() {
@@ -266,27 +250,18 @@ UInt.prototype.to_hex = function() {
return null;
}
return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase();
return utils.arrayToHex(this.to_bytes());
};
UInt.prototype.to_json = UInt.prototype.to_hex;
// Convert from internal form.
UInt.prototype.to_bits = function() {
if (!this.is_valid()) {
return null;
}
return this._value.toBits(this.constructor.width * 8);
};
UInt.prototype.to_bn = function() {
if (!this.is_valid()) {
return null;
}
const bits = this.to_bits();
return sjcl.bn.fromBits(bits);
return sjclcodec.bytes.toBits(this.to_bytes());
};
exports.UInt = UInt;

View File

@@ -77,7 +77,7 @@ function hexToArray(h) {
function arrayToHex(a) {
return a.map(function(byteValue) {
const hex = byteValue.toString(16);
const hex = byteValue.toString(16).toUpperCase();
return hex.length > 1 ? hex : '0' + hex;
}).join('');
}
@@ -166,6 +166,4 @@ exports.fromTimestamp = fromTimestamp;
exports.getMantissaDecimalString = getMantissaDecimalString;
exports.getMantissa16FromString = getMantissa16FromString;
exports.sjcl = require('sjcl-extended');
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,6 +1,8 @@
'use strict';
const _ = require('lodash');
const core = require('./core');
const RippleAPI = require('./api');
module.exports = _.assign({}, core, {RippleAPI: RippleAPI});
module.exports = {
RippleAPI,
_DEPRECATED: core // WARNING: this will be removed soon
};

1
src/js

Submodule src/js deleted from 16dde36fa2

View File

@@ -2,15 +2,20 @@
'use strict';
const _ = require('lodash');
const assert = require('assert-diff');
const path = require('path');
const setupAPI = require('./setup-api');
const RippleAPI = require('ripple-api').RippleAPI;
const common = RippleAPI._PRIVATE.common;
const fixtures = require('./fixtures/api');
const requests = fixtures.requests;
const responses = fixtures.responses;
const addresses = require('./fixtures/addresses');
const hashes = require('./fixtures/hashes');
const MockPRNG = require('./mock-prng');
const sjcl = require('../src').sjcl;
const address = addresses.ACCOUNT;
const validate = common.validate;
const utils = RippleAPI._PRIVATE.ledgerUtils;
const ledgerClosed = require('./fixtures/api/rippled/ledger-close-newer');
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
const orderbook = {
base: {
@@ -23,21 +28,13 @@ const orderbook = {
}
};
function checkResult(expected, done, error, response) {
if (error) {
done(error);
return;
}
function checkResult(expected, schemaName, response) {
// console.log(JSON.stringify(response, null, 2));
assert.deepEqual(response, expected);
done();
}
function withDeterministicPRNG(f) {
const prng = sjcl.random;
sjcl.random = new MockPRNG();
f();
sjcl.random = prng;
if (schemaName) {
schemaValidator.schemaValidate(schemaName, response);
}
return response;
}
describe('RippleAPI', function() {
@@ -45,156 +42,406 @@ describe('RippleAPI', function() {
beforeEach(setupAPI.setup);
afterEach(setupAPI.teardown);
it('preparePayment', function(done) {
this.api.preparePayment(address, requests.preparePayment, instructions,
_.partial(checkResult, responses.preparePayment, done));
it('preparePayment', function() {
const localInstructions = _.defaults({
maxFee: '0.000012'
}, instructions);
return this.api.preparePayment(
address, requests.preparePayment, localInstructions).then(
_.partial(checkResult, responses.preparePayment, 'tx'));
});
it('preparePayment with all options specified', function(done) {
this.api.preparePayment(address, requests.preparePaymentAllOptions,
instructions,
_.partial(checkResult, responses.preparePaymentAllOptions, done));
it('preparePayment with all options specified', function() {
const localInstructions = {
maxLedgerVersion: this.api.getLedgerVersion() + 100,
fee: '0.000012'
};
return this.api.preparePayment(
address, requests.preparePaymentAllOptions, localInstructions).then(
_.partial(checkResult, responses.preparePaymentAllOptions, 'tx'));
});
it('preparePayment without counterparty set', function(done) {
this.api.preparePayment(address, requests.preparePaymentNoCounterparty,
instructions,
_.partial(checkResult, responses.preparePaymentNoCounterparty, done));
it('preparePayment without counterparty set', function() {
const localInstructions = _.defaults({sequence: 23}, instructions);
return this.api.preparePayment(
address, requests.preparePaymentNoCounterparty, localInstructions).then(
_.partial(checkResult, responses.preparePaymentNoCounterparty, 'tx'));
});
it('prepareOrder - buy order', function(done) {
this.api.prepareOrder(address, requests.prepareOrder, instructions,
_.partial(checkResult, responses.prepareOrder, done));
it('prepareOrder - buy order', function() {
return this.api.prepareOrder(address, requests.prepareOrder, instructions)
.then(_.partial(checkResult, responses.prepareOrder, 'tx'));
});
it('prepareOrder - sell order', function(done) {
this.api.prepareOrder(address, requests.prepareOrderSell, instructions,
_.partial(checkResult, responses.prepareOrderSell, done));
it('prepareOrder - sell order', function() {
return this.api.prepareOrder(
address, requests.prepareOrderSell, instructions).then(
_.partial(checkResult, responses.prepareOrderSell, 'tx'));
});
it('prepareOrderCancellation', function(done) {
this.api.prepareOrderCancellation(address, 23, instructions,
_.partial(checkResult, responses.prepareOrderCancellation, done));
it('prepareOrderCancellation', function() {
return this.api.prepareOrderCancellation(address, 23, instructions).then(
_.partial(checkResult, responses.prepareOrderCancellation, 'tx'));
});
it('prepareTrustline', function(done) {
this.api.prepareTrustline(address, requests.prepareTrustline,
instructions, _.partial(checkResult, responses.prepareTrustline, done));
it('prepareTrustline - simple', function() {
return this.api.prepareTrustline(
address, requests.prepareTrustline.simple, instructions).then(
_.partial(checkResult, responses.prepareTrustline.simple, 'tx'));
});
it('prepareSettings', function(done) {
this.api.prepareSettings(address, requests.prepareSettings, instructions,
_.partial(checkResult, responses.prepareSettings.flags, done));
it('prepareTrustline - complex', function() {
return this.api.prepareTrustline(
address, requests.prepareTrustline.complex, instructions).then(
_.partial(checkResult, responses.prepareTrustline.complex, 'tx'));
});
it('prepareSettings - regularKey', function(done) {
it('prepareSettings', function() {
return this.api.prepareSettings(
address, requests.prepareSettings, instructions).then(
_.partial(checkResult, responses.prepareSettings.flags, 'tx'));
});
it('prepareSettings - regularKey', function() {
const regularKey = {regularKey: 'rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD'};
this.api.prepareSettings(address, regularKey, instructions,
_.partial(checkResult, responses.prepareSettings.regularKey, done));
return this.api.prepareSettings(address, regularKey, instructions).then(
_.partial(checkResult, responses.prepareSettings.regularKey, 'tx'));
});
it('prepareSettings - flag set', function() {
const settings = {requireDestinationTag: true};
return this.api.prepareSettings(address, settings, instructions).then(
_.partial(checkResult, responses.prepareSettings.flagSet, 'tx'));
});
it('prepareSettings - flag clear', function() {
const settings = {requireDestinationTag: false};
return this.api.prepareSettings(address, settings, instructions).then(
_.partial(checkResult, responses.prepareSettings.flagClear, 'tx'));
});
it('prepareSettings - string field clear', function() {
const settings = {walletLocator: null};
return this.api.prepareSettings(address, settings, instructions).then(
_.partial(checkResult, responses.prepareSettings.fieldClear, 'tx'));
});
it('prepareSettings - integer field clear', function() {
const settings = {walletSize: null};
return this.api.prepareSettings(address, settings, instructions)
.then(data => {
assert(data);
assert.strictEqual(data.WalletSize, 0);
});
});
it('prepareSettings - set transferRate', function() {
const settings = {transferRate: 1};
return this.api.prepareSettings(address, settings, instructions).then(
_.partial(checkResult, responses.prepareSettings.setTransferRate, 'tx'));
});
it('sign', function() {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
withDeterministicPRNG(() => {
const result = this.api.sign(requests.sign, secret);
assert.deepEqual(result, responses.sign);
const result = this.api.sign(requests.sign, secret);
assert.deepEqual(result, responses.sign);
schemaValidator.schemaValidate('sign', result);
});
it('submit', function() {
return this.api.submit(responses.sign.signedTransaction).then(
_.partial(checkResult, responses.submit, 'submit'));
});
it('getBalances', function() {
return this.api.getBalances(address).then(
_.partial(checkResult, responses.getBalances, 'getBalances'));
});
it('getTransaction - payment', function() {
return this.api.getTransaction(hashes.VALID_TRANSACTION_HASH).then(
_.partial(checkResult, responses.getTransaction.payment,
'getTransaction'));
});
it('getTransaction - settings', function() {
const hash =
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.settings,
'getTransaction'));
});
it('getTransaction - order', function() {
const hash =
'10A6FB4A66EE80BED46AAE4815D7DC43B97E944984CCD5B93BCF3F8538CABC51';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.order,
'getTransaction'));
});
it('getTransaction - order cancellation', function() {
const hash =
'809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.orderCancellation,
'getTransaction'));
});
it('getTransaction - trustline set', function() {
const hash =
'635A0769BD94710A1F6A76CDE65A3BC661B20B798807D1BBBDADCEA26420538D';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.trustline,
'getTransaction'));
});
it('getTransaction - trustline frozen off', function() {
const hash =
'FE72FAD0FA7CA904FB6C633A1666EDF0B9C73B2F5A4555D37EEF2739A78A531B';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.trustlineFrozenOff,
'getTransaction'));
});
it('getTransaction - trustline no quality', function() {
const hash =
'BAF1C678323C37CCB7735550C379287667D8288C30F83148AD3C1CB019FC9002';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.trustlineNoQuality,
'getTransaction'));
});
it('getTransaction - not validated', function() {
const hash =
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA10';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.notValidated,
'getTransaction'));
});
it('getTransaction - tracking on', function() {
const hash =
'8925FC8844A1E930E2CC76AD0A15E7665AFCC5425376D548BB1413F484C31B8C';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.trackingOn,
'getTransaction'));
});
it('getTransaction - tracking off', function() {
const hash =
'C8C5E20DFB1BF533D0D81A2ED23F0A3CBD1EF2EE8A902A1D760500473CC9C582';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.trackingOff,
'getTransaction'));
});
it('getTransaction - set regular key', function() {
const hash =
'278E6687C1C60C6873996210A6523564B63F2844FB1019576C157353B1813E60';
return this.api.getTransaction(hash).then(
_.partial(checkResult, responses.getTransaction.setRegularKey,
'getTransaction'));
});
it('getTransaction - not found in range', function() {
const hash =
'809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E';
const options = {
minLedgerVersion: 32570,
maxLedgerVersion: 32571
};
return this.api.getTransaction(hash, options).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
});
});
it('submit', function(done) {
this.api.submit(responses.sign.signedTransaction,
_.partial(checkResult, responses.submit, done));
it('getTransaction - not found by hash', function() {
const hash = hashes.NOTFOUND_TRANSACTION_HASH;
return this.api.getTransaction(hash).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
});
});
it('getBalances', function(done) {
this.api.getBalances(address, {},
_.partial(checkResult, responses.getBalances, done));
it('getTransaction - missing ledger history', function() {
const hash = hashes.NOTFOUND_TRANSACTION_HASH;
// make gaps in history
this.api.remote.getServer().emit('message', ledgerClosed);
return this.api.getTransaction(hash).then(() => {
assert(false, 'Should throw MissingLedgerHistoryError');
}).catch(error => {
assert(error instanceof this.api.errors.MissingLedgerHistoryError);
});
});
it('getTransaction - payment', function(done) {
this.api.getTransaction(hashes.VALID_TRANSACTION_HASH, {},
_.partial(checkResult, responses.getTransaction.payment, done));
});
it('getTransaction - settings', function(done) {
it('getTransaction - ledger_index not found', function() {
const hash =
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B';
this.api.getTransaction(hash, {},
_.partial(checkResult, responses.getTransaction.settings, done));
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11';
return this.api.getTransaction(hash).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
assert(error.message.indexOf('ledger_index') !== -1);
});
});
it('getTransaction - order', function(done) {
it('getTransaction - transaction ledger not found', function() {
const hash =
'10A6FB4A66EE80BED46AAE4815D7DC43B97E944984CCD5B93BCF3F8538CABC51';
this.api.getTransaction(hash, {},
_.partial(checkResult, responses.getTransaction.order, done));
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12';
return this.api.getTransaction(hash).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
assert(error.message.indexOf('ledger not found') !== -1);
});
});
it('getTransaction - order cancellation', function(done) {
it('getTransaction - ledger missing close time', function() {
const hash =
'809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E';
this.api.getTransaction(hash, {},
_.partial(checkResult, responses.getTransaction.orderCancellation, done));
'0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04';
return this.api.getTransaction(hash).then(() => {
assert(false, 'Should throw ApiError');
}).catch(error => {
assert(error instanceof this.api.errors.ApiError);
});
});
it('getTransaction - trustline set', function(done) {
const hash =
'635A0769BD94710A1F6A76CDE65A3BC661B20B798807D1BBBDADCEA26420538D';
this.api.getTransaction(hash, {},
_.partial(checkResult, responses.getTransaction.trustline, done));
});
it('getTransactions', function(done) {
it('getTransactions', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 2};
this.api.getTransactions(address, options,
_.partial(checkResult, responses.getTransactions, done));
return this.api.getTransactions(address, options).then(
_.partial(checkResult, responses.getTransactions,
'getTransactions'));
});
it('getTransactions - earliest first', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 2,
earliestFirst: true
};
const expected = _.cloneDeep(responses.getTransactions)
.sort(utils.compareTransactions);
return this.api.getTransactions(address, options).then(
_.partial(checkResult, expected, 'getTransactions'));
});
it('getTransactions - earliest first with start option', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 2,
start: hashes.VALID_TRANSACTION_HASH,
earliestFirst: true
};
return this.api.getTransactions(address, options).then(data => {
assert.strictEqual(data.length, 0);
});
});
it('getTransactions - gap', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 2,
maxLedgerVersion: 348858000
};
return this.api.getTransactions(address, options).then(() => {
assert(false, 'Should throw MissingLedgerHistoryError');
}).catch(error => {
assert(error instanceof this.api.errors.MissingLedgerHistoryError);
});
});
it('getTransactions - tx not found', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 2,
start: hashes.NOTFOUND_TRANSACTION_HASH,
counterparty: address
};
return this.api.getTransactions(address, options).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
});
});
it('getTransactions - filters', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 10,
excludeFailures: true,
counterparty: addresses.ISSUER
};
return this.api.getTransactions(address, options).then(data => {
assert.strictEqual(data.length, 10);
assert(_.every(data, t => t.type === 'payment' || t.type === 'order'));
assert(_.every(data, t => t.outcome.result === 'tesSUCCESS'));
});
});
it('getTransactions - filters for incoming', function() {
const options = {types: ['payment', 'order'], initiated: false, limit: 10,
excludeFailures: true,
counterparty: addresses.ISSUER
};
return this.api.getTransactions(address, options).then(data => {
assert.strictEqual(data.length, 10);
assert(_.every(data, t => t.type === 'payment' || t.type === 'order'));
assert(_.every(data, t => t.outcome.result === 'tesSUCCESS'));
});
});
// this is the case where core.RippleError just falls
// through the api to the user
it('getTransactions - error', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 13};
return this.api.getTransactions(address, options).then(() => {
assert(false, 'Should throw RippleError');
}).catch(error => {
assert(error instanceof common.core.RippleError);
});
});
// TODO: this doesn't test much, just that it doesn't crash
it('getTransactions with start option', function(done) {
it('getTransactions with start option', function() {
const options = {
start: hashes.VALID_TRANSACTION_HASH,
earliestFirst: false,
limit: 2
};
this.api.getTransactions(address, options,
_.partial(checkResult, responses.getTransactions, done));
return this.api.getTransactions(address, options).then(
_.partial(checkResult, responses.getTransactions, 'getTransactions'));
});
it('getTrustlines', function(done) {
it('getTrustlines', function() {
const options = {currency: 'USD'};
this.api.getTrustlines(address, options,
_.partial(checkResult, responses.getTrustlines, done));
return this.api.getTrustlines(address, options).then(
_.partial(checkResult, responses.getTrustlines, 'getTrustlines'));
});
it('generateWallet', function() {
withDeterministicPRNG(() => {
assert.deepEqual(this.api.generateWallet(), responses.generateWallet);
});
it('generateAddress', function() {
function random() {
return _.fill(Array(16), 0);
}
assert.deepEqual(this.api.generateAddress({random}),
responses.generateAddress);
});
it('getSettings', function(done) {
this.api.getSettings(address, {},
_.partial(checkResult, responses.getSettings, done));
it('getSettings', function() {
return this.api.getSettings(address).then(
_.partial(checkResult, responses.getSettings, 'getSettings'));
});
it('getAccountInfo', function(done) {
this.api.getAccountInfo(address, {},
_.partial(checkResult, responses.getAccountInfo, done));
it('getAccountInfo', function() {
return this.api.getAccountInfo(address).then(
_.partial(checkResult, responses.getAccountInfo, 'getAccountInfo'));
});
it('getOrders', function(done) {
this.api.getOrders(address, {},
_.partial(checkResult, responses.getOrders, done));
it('getOrders', function() {
return this.api.getOrders(address).then(
_.partial(checkResult, responses.getOrders, 'getOrders'));
});
it('getOrderbook', function(done) {
this.api.getOrderbook(address, orderbook, {},
_.partial(checkResult, responses.getOrderbook, done));
it('getOrderbook', function() {
return this.api.getOrderbook(address, orderbook).then(
_.partial(checkResult, responses.getOrderbook, 'getOrderbook'));
});
it('getOrderbook - sorted so that best deals come first', function(done) {
this.api.getOrderbook(address, orderbook, {}, (error, data) => {
it('getOrderbook - sorted so that best deals come first', function() {
return this.api.getOrderbook(address, orderbook).then(data => {
const bidRates = data.bids.map(bid => bid.properties.makerExchangeRate);
const askRates = data.asks.map(ask => ask.properties.makerExchangeRate);
// makerExchangeRate = quality = takerPays.value/takerGets.value
@@ -202,12 +449,11 @@ describe('RippleAPI', function() {
// bids and asks should be sorted so that the best deals come first
assert.deepEqual(_.sortBy(bidRates, x => Number(x)), bidRates);
assert.deepEqual(_.sortBy(askRates, x => Number(x)), askRates);
done();
});
});
it('getOrderbook - currency & counterparty are correct', function(done) {
this.api.getOrderbook(address, orderbook, {}, (error, data) => {
it('getOrderbook - currency & counterparty are correct', function() {
return this.api.getOrderbook(address, orderbook).then(data => {
const orders = _.flatten([data.bids, data.asks]);
_.forEach(orders, order => {
const quantity = order.specification.quantity;
@@ -218,54 +464,358 @@ describe('RippleAPI', function() {
assert.strictEqual(totalPrice.currency, counter.currency);
assert.strictEqual(totalPrice.counterparty, counter.counterparty);
});
done();
});
});
it('getOrderbook - direction is correct for bids and asks', function(done) {
this.api.getOrderbook(address, orderbook, {}, (error, data) => {
it('getOrderbook - direction is correct for bids and asks', function() {
return this.api.getOrderbook(address, orderbook).then(data => {
assert(_.every(data.bids, bid => bid.specification.direction === 'buy'));
assert(_.every(data.asks, ask => ask.specification.direction === 'sell'));
done();
});
});
it('getServerInfo', function(done) {
this.api.getServerInfo(
_.partial(checkResult, responses.getServerInfo, done));
it('getServerInfo', function() {
return this.api.getServerInfo().then(
_.partial(checkResult, responses.getServerInfo, 'getServerInfo'));
});
it('getServerInfo - error', function() {
this.mockRippled.returnErrorOnServerInfo = true;
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw NetworkError');
}).catch(error => {
assert(error instanceof this.api.errors.NetworkError);
assert(error.message.indexOf('too much load') !== -1);
});
});
it('getFee', function() {
assert.strictEqual(this.api.getFee(), '0.000012');
});
it('disconnect & isConnected', function(done) {
it('disconnect & isConnected', function() {
assert.strictEqual(this.api.isConnected(), true);
this.api.disconnect(() => {
return this.api.disconnect().then(() => {
assert.strictEqual(this.api.isConnected(), false);
done();
});
});
it('getPaths', function(done) {
const pathfind = {
source: {
address: address
},
destination: {
address: addresses.OTHER_ACCOUNT,
amount: {
currency: 'USD',
counterparty: addresses.ISSUER,
value: '100'
}
}
};
this.api.getPaths(pathfind,
_.partial(checkResult, responses.getPaths, done));
it('getPaths', function() {
return this.api.getPaths(requests.getPaths.normal).then(
_.partial(checkResult, responses.getPaths.XrpToUsd, 'getPaths'));
});
// @TODO
// need decide what to do with currencies/XRP:
// if add 'XRP' in currencies, then there will be exception in
// xrpToDrops function (called from toRippledAmount)
it('getPaths USD 2 USD', function() {
return this.api.getPaths(requests.getPaths.UsdToUsd).then(
_.partial(checkResult, responses.getPaths.UsdToUsd, 'getPaths'));
});
it('getPaths XRP 2 XRP', function() {
return this.api.getPaths(requests.getPaths.XrpToXrp).then(
_.partial(checkResult, responses.getPaths.XrpToXrp, 'getPaths'));
});
it('getPaths - XRP 2 XRP - not enough', function() {
return this.api.getPaths(requests.getPaths.XrpToXrpNotEnough).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
});
});
it('getPaths - does not accept currency', function() {
return this.api.getPaths(requests.getPaths.NotAcceptCurrency).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
});
});
it('getPaths - no paths', function() {
return this.api.getPaths(requests.getPaths.NoPaths).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
});
});
it('getPaths - no paths with source currencies', function() {
const pathfind = requests.getPaths.NoPathsWithCurrencies;
return this.api.getPaths(pathfind).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
});
});
it('getLedgerVersion', function() {
assert.strictEqual(this.api.getLedgerVersion(), 8819951);
});
it('getLedger', function() {
return this.api.getLedger().then(
_.partial(checkResult, responses.getLedger.header, 'getLedger'));
});
it('getLedger - full, then computeLedgerHash', function() {
const request = {
includeTransactions: true,
includeState: true,
includeAllData: true,
ledgerVersion: 38129
};
return this.api.getLedger(request).then(
_.partial(checkResult, responses.getLedger.full, 'getLedger'))
.then(response => {
const ledger = _.assign({}, response,
{parentCloseTime: response.closeTime});
const hash = this.api.computeLedgerHash(ledger);
assert.strictEqual(hash,
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E');
});
});
it('ledger utils - compareTransactions', function() {
let first = {outcome: {ledgerVersion: 1, indexInLedger: 100}};
let second = {outcome: {ledgerVersion: 1, indexInLedger: 200}};
assert.strictEqual(utils.compareTransactions(first, second), -1);
first = {outcome: {ledgerVersion: 1, indexInLedger: 100}};
second = {outcome: {ledgerVersion: 1, indexInLedger: 100}};
assert.strictEqual(utils.compareTransactions(first, second), 0);
first = {outcome: {ledgerVersion: 1, indexInLedger: 200}};
second = {outcome: {ledgerVersion: 1, indexInLedger: 100}};
assert.strictEqual(utils.compareTransactions(first, second), 1);
});
it('ledger utils - renameCounterpartyToIssuer', function() {
assert.strictEqual(utils.renameCounterpartyToIssuer(undefined), undefined);
const amountArg = {issuer: '1'};
assert.deepEqual(utils.renameCounterpartyToIssuer(amountArg), amountArg);
});
it('ledger utils - getRecursive', function(done) {
function getter(marker, limit, callback) {
if (marker === undefined) {
callback(null, {marker: 'A', limit: limit, results: [1]});
} else {
callback(new Error(), null);
}
}
utils.getRecursive(getter, 10, (error) => {
assert(error instanceof Error);
done();
});
});
describe('schema-validator', function() {
beforeEach(function() {
const schema = schemaValidator.loadSchema(path.join(__dirname,
'./fixtures/schemas/ledgerhash.json'));
schemaValidator.SCHEMAS.ledgerhash = schema;
});
it('valid', function() {
assert.doesNotThrow(function() {
schemaValidator.schemaValidate('ledgerhash',
'0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A0F');
});
});
it('invalid', function() {
assert.throws(function() {
schemaValidator.schemaValidate('ledgerhash', 'invalid');
}, this.api.errors.ValidationError);
});
it('invalid - empty value', function() {
assert.throws(function() {
schemaValidator.schemaValidate('ledgerhash', '');
}, this.api.errors.ValidationError);
});
it('load schema error', function() {
assert.throws(function() {
schemaValidator.loadSchema('/bad/file/name');
}, Error);
});
it('schema not found error', function() {
assert.throws(function() {
schemaValidator.schemaValidate('unexisting', 'anything');
}, /schema not found/);
});
});
describe('validator', function() {
it('validateLedgerRange', function() {
const options = {
minLedgerVersion: 20000,
maxLedgerVersion: 10000
};
assert.throws(_.partial(validate.getTransactionsOptions, options),
this.api.errors.ValidationError);
assert.throws(_.partial(validate.getTransactionsOptions, options),
/minLedgerVersion must not be greater than maxLedgerVersion/);
});
it('addressAndSecret', function() {
const noSecret = {address: address};
assert.throws(_.partial(validate.addressAndSecret, noSecret),
this.api.errors.ValidationError);
assert.throws(_.partial(validate.addressAndSecret, noSecret),
/Parameter missing/);
const badSecret = {address: address, secret: 'sbad'};
assert.throws(_.partial(validate.addressAndSecret, badSecret),
this.api.errors.ValidationError);
const goodWallet = {address: 'rpZMK8hwyrBvLorFNWHRCGt88nCJWbixur',
secret: 'shzjfakiK79YQdMjy4h8cGGfQSV6u'
};
assert.doesNotThrow(_.partial(validate.addressAndSecret, goodWallet));
});
it('secret', function() {
assert.doesNotThrow(_.partial(validate.secret,
'shzjfakiK79YQdMjy4h8cGGfQSV6u'));
assert.throws(_.partial(validate.secret, 1),
/Invalid parameter/);
assert.throws(_.partial(validate.secret, ''),
this.api.errors.ValidationError);
assert.throws(_.partial(validate.secret, 's!!!'),
this.api.errors.ValidationError);
assert.throws(_.partial(validate.secret, 'passphrase'),
this.api.errors.ValidationError);
// 32 0s is a valid hex repr of seed bytes
const hex = new Array(33).join('0');
assert.throws(_.partial(validate.secret, hex),
this.api.errors.ValidationError);
});
});
describe('common utils', function() {
it('wrapCatch', function(done) {
common.wrapCatch(function() {
throw new Error('error');
})(function(error) {
assert(error instanceof Error);
done();
});
});
it('convertExceptions', function() {
assert.throws(common.convertExceptions(function() {
throw new Error('fall through');
}), this.api.errors.ApiError);
assert.throws(common.convertExceptions(function() {
throw new Error('fall through');
}), /fall through/);
});
});
describe('common errors', function() {
it('TransactionError', function() {
// TransactionError is not used anywhere, so just test its creation
assert.throws(function() {
throw new common.errors.TransactionError('fall through');
}, this.api.errors.TransactionError);
assert.throws(function() {
throw new common.errors.TransactionError('fall through');
}, /fall through/);
});
it('TimeOutError', function() {
// TimeOutError is not used anywhere, so just test its creation
assert.throws(function() {
throw new common.errors.TimeOutError('fall through');
}, this.api.errors.TimeOutError);
assert.throws(function() {
throw new common.errors.TimeOutError('fall through');
}, /fall through/);
});
it('RippledNetworkError', function() {
assert.throws(function() {
throw new common.errors.RippledNetworkError();
}, /Cannot connect to rippled/);
});
});
});
describe('RippleAPI - offline', function() {
it('prepareSettings and sign', function() {
const api = new RippleAPI();
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const settings = requests.prepareSettings;
const instructions = {
sequence: 23,
maxLedgerVersion: 8820051,
fee: '0.000012'
};
return api.prepareSettings(address, settings, instructions).then(txJSON => {
assert.deepEqual(txJSON, responses.prepareSettings.flags);
assert.deepEqual(api.sign(txJSON, secret), responses.sign);
});
});
it('computeLedgerHash', function() {
const api = new RippleAPI();
const header = requests.computeLedgerHash.header;
const ledgerHash = api.computeLedgerHash(header);
assert.strictEqual(ledgerHash,
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349');
});
it('computeLedgerHash - with transactions', function() {
const api = new RippleAPI();
const header = _.omit(requests.computeLedgerHash.header,
'transactionHash');
header.rawTransactions = JSON.stringify(
requests.computeLedgerHash.transactions);
const ledgerHash = api.computeLedgerHash(header);
assert.strictEqual(ledgerHash,
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349');
});
it('computeLedgerHash - incorrent transaction_hash', function() {
const api = new RippleAPI();
const header = _.assign({}, requests.computeLedgerHash.header,
{transactionHash:
'325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C9'});
header.rawTransactions = JSON.stringify(
requests.computeLedgerHash.transactions);
assert.throws(() => api.computeLedgerHash(header));
});
it('isValidAddress - valid', function() {
const api = new RippleAPI();
assert(api.isValidAddress(address));
});
it('isValidAddress - invalid', function() {
const api = new RippleAPI();
assert(!api.isValidAddress(address.slice(0, -1) + 'a'));
});
it('isValidAddress - invalid - hex representation', function() {
const api = new RippleAPI();
const hex = '6e3efa86a5eb0a3c5dc9beb3a204783bb00e1913';
assert(!api.isValidAddress(hex));
});
});

View File

@@ -31,11 +31,11 @@ describe('Base', function() {
describe('decode_check', function() {
it('rrrrrrrrrrrrrrrrrrrrrhoLvTp', function() {
const decoded = Base.decode_check(0, 'rrrrrrrrrrrrrrrrrrrrrhoLvTp');
assert(decoded.equals(0));
assert(decoded.cmpn(0) === 0);
});
it('rrrrrrrrrrrrrrrrrrrrBZbvji', function() {
const decoded = Base.decode_check(0, 'rrrrrrrrrrrrrrrrrrrrBZbvji');
assert(decoded.equals(1));
assert(decoded.cmpn(1) === 0);
});
});
describe('decode-encode identity', function() {

View File

@@ -0,0 +1,474 @@
[
{
"hash": "f8f337dee5d5b238a10af4a4d56926ba26c83ee7af5a5a6474340c56f9252df3",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 1608,
"LastLedgerSequence": 15202446,
"Amount": "120000000",
"Fee": "15000",
"SigningPubKey": "03BC0973F997BC6384BE455B163519A3E96BC2D725C37F7172D5FED5DD38E2A357",
"TxnSignature": "3045022100D80A1802B00AEEF9FDFDE594B0D568217A312D54E6337B8519C0D699841EFB96022067F6913B13D0EC2354C5A67CE0A41AE4181A09CD08A1BB0638D128D357961006",
"Account": "rDPL68aNpdfp9h59R4QT5R6B1Z2W9oRc51",
"Destination": "rE4S4Xw8euysJ3mt7gmK8EhhYEwmALpb3R"
},
"meta": {
"TransactionIndex": 6,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202381,
"PreviousTxnID": "8FFB65C6907C9679C5F8AADA97072CD1B8FE4955FC6A614AC87408AE7C9088AD",
"LedgerIndex": "B07B367ABF05243A536986DEC74684E983BBBDDF443ADE9CDC43A22D6E6A1420",
"PreviousFields": {
"Sequence": 1608,
"Balance": "61455842701"
},
"FinalFields": {
"Flags": 0,
"Sequence": 1609,
"OwnerCount": 0,
"Balance": "61335827701",
"Account": "rDPL68aNpdfp9h59R4QT5R6B1Z2W9oRc51"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202438,
"PreviousTxnID": "B01591A2353CD39EFAC989D542EE37591F60CF9BB2B66526C8C958774813407E",
"LedgerIndex": "F77EB82FA9593E695F22155C00C569A570CF32316BEFDFF0B16BADAFF2ACFF19",
"PreviousFields": {
"Balance": "26762033252"
},
"FinalFields": {
"Flags": 0,
"Sequence": 6448,
"OwnerCount": 3,
"Balance": "26882033252",
"Account": "rE4S4Xw8euysJ3mt7gmK8EhhYEwmALpb3R"
}
}
}
],
"TransactionResult": "tesSUCCESS"
}
},
{
"hash": "f8d5de632b1d8b64e577c46912cce483d6df4fd4e2cf4a3d586a099de3b27021",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 18874,
"LastLedgerSequence": 15202446,
"Amount": "120000000",
"Fee": "15000",
"SigningPubKey": "035D097E75D4B35345CEB30F9B1D18CB81165FE6ADD02481AA5B02B5F9C8107EE1",
"TxnSignature": "304402203D80E8BC71908AB345948AB71FB7B8DE239DD79636D96D3C5BDA2B2F192A5EEA0220686413D69BF0D813FC61DABD437AEFAAE69925D3E10FCD5B2C4D90B5AF7B883D",
"Account": "rnHScgV6wSP9sR25uYWiMo3QYNA5ybQ7cH",
"Destination": "rwnnfHDaEAwXaVji52cWWizbHVMs2Cz5K9"
},
"meta": {
"TransactionIndex": 5,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202429,
"PreviousTxnID": "B1F39887411C1771998F38502EDF33170F9F5659503DB9DE642EBA896B5F198B",
"LedgerIndex": "2AAA3361C593C4DE7ABD9A607B3CA7070A3F74E3C3F2FDE4DDB9484E47ED056E",
"PreviousFields": {
"Sequence": 18874,
"Balance": "13795295558367"
},
"FinalFields": {
"Flags": 0,
"Sequence": 18875,
"OwnerCount": 0,
"Balance": "13795175543367",
"Account": "rnHScgV6wSP9sR25uYWiMo3QYNA5ybQ7cH"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202416,
"PreviousTxnID": "00CF9C7BE3EBAF76893C6A3F6D10B4D89F8D856C97B9D44938CF1682132ACEB8",
"LedgerIndex": "928582D6F6942B18F3462FA04BA99F476B64FEB9921BFAD583182DC28CB74187",
"PreviousFields": {
"Balance": "17674359316"
},
"FinalFields": {
"Flags": 0,
"Sequence": 1710,
"OwnerCount": 0,
"Balance": "17794359316",
"Account": "rwnnfHDaEAwXaVji52cWWizbHVMs2Cz5K9"
}
}
}
],
"TransactionResult": "tesSUCCESS"
}
},
{
"hash": "e9004490a92413e92dacd621ac73fd434a8950c350f7572ffeaf4d6aaf8fc288",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 1615,
"LastLedgerSequence": 15202446,
"Amount": "400000000",
"Fee": "15000",
"SigningPubKey": "03ACFAA11628C558AB5E7FA64705F442BDAABA6E9D318B30E010BC87CDEA8D1D7D",
"TxnSignature": "3045022100A3530C2E983FB05DFF27172C649494291F7BEBA2E6A59EEAF945CB9728D1DB5E022015BCA0E9D69760224DD7C2B68F3BC1F239D89C3397161AA3901C2E04EE31C18F",
"Account": "razcSDpwds1aTeqDphqzBr7ay1ZELYAWTm",
"Destination": "rhuqJAE2UfhGCvkR7Ve35bvm39JmRvFML4"
},
"meta": {
"TransactionIndex": 4,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202394,
"PreviousTxnID": "99E8F8988390F5A8DF69BBA4F04705E5085EE91B27583D28210D37B7513F10BB",
"LedgerIndex": "17CF549DFC0813DDC44559C89E99B4C1D033D59FF379AD948CBEC141F179293D",
"PreviousFields": {
"Sequence": 1615,
"Balance": "45875786250"
},
"FinalFields": {
"Flags": 0,
"Sequence": 1616,
"OwnerCount": 0,
"Balance": "45475771250",
"Account": "razcSDpwds1aTeqDphqzBr7ay1ZELYAWTm"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202438,
"PreviousTxnID": "9EC0784393DA95BB3B38FABC59FEFEE34BA8487DD892B9EAC1D70E483D1B0FA6",
"LedgerIndex": "EB13399E9A69F121BEDA810F1AE9CB4023B4B09C5055CB057B572029B2FC8DD4",
"PreviousFields": {
"Balance": "76953067090"
},
"FinalFields": {
"Flags": 0,
"Sequence": 601,
"OwnerCount": 4,
"Balance": "77353067090",
"Account": "rhuqJAE2UfhGCvkR7Ve35bvm39JmRvFML4"
}
}
}
],
"TransactionResult": "tesSUCCESS"
}
},
{
"hash": "d44bff924d23211b82b8f604af6d92f260f8dd13103a96f03e48825c4a978fd6",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 1674,
"LastLedgerSequence": 15202446,
"Amount": "800000000",
"Fee": "15000",
"SigningPubKey": "028F28D78FDA74222F4008F012247DF3BBD42B90CE4CFD87E29598196108E91B52",
"TxnSignature": "3044022065A003194D91E774D180BE47D4E086BB2624BC8F6DB7C655E135D5C6C03BBC7C02205DC961C2B7A06D701B29C2116ACF6F84CC84205FF44411576C15507852ECC31C",
"Account": "rQGLp9nChtWkdgcHjj6McvJithN2S2HJsP",
"Destination": "rEUubanepAAugnNJY1gxEZLDnk9W5NCoFU"
},
"meta": {
"TransactionIndex": 3,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202409,
"PreviousTxnID": "6A9B73C13B8A74BCDB64B5ADFE3D8FFEAC7928B82CFD6C9A35254D7798AD0688",
"LedgerIndex": "D1A7795E8E997E7DE65D64283FD7CEEB5E43C2E5C4A794C2CFCEC6724E03F464",
"PreviousFields": {
"Sequence": 1674,
"Balance": "8774844732"
},
"FinalFields": {
"Flags": 0,
"Sequence": 1675,
"OwnerCount": 0,
"Balance": "7974829732",
"Account": "rQGLp9nChtWkdgcHjj6McvJithN2S2HJsP"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202388,
"PreviousTxnID": "ECE994DA817228D9170D22C01CE1BF5B17FFE1AE6404FF215719C1049E9939E0",
"LedgerIndex": "E5EA9215A6D41C4E20C831ACE436E5B75F9BA2A9BD4325BA65BD9D44F5E13A08",
"PreviousFields": {
"Balance": "9077529029"
},
"FinalFields": {
"Flags": 0,
"Sequence": 1496,
"OwnerCount": 0,
"Balance": "9877529029",
"Account": "rEUubanepAAugnNJY1gxEZLDnk9W5NCoFU"
}
}
}
],
"TransactionResult": "tesSUCCESS"
}
},
{
"hash": "c978d915bfb17687335cbfc4b207d9e7213bcee35b468c2eee016cdce4edb6e4",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "OfferCreate",
"Sequence": 289444,
"OfferSequence": 289443,
"LastLedgerSequence": 15202441,
"TakerPays": {
"value": "19.99999999991",
"currency": "EUR",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": {
"value": "20.88367500010602",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"Fee": "10000",
"SigningPubKey": "024D129D4F5A12D4C5A9E9D1E4AC447BBE3496F182FAE82F7709C7EB9F12DBC697",
"TxnSignature": "3044022041EBE6B06BA493867F4FFBD72E5D6253F97306E1E82DABDF9649E15B1151B59F0220539C589F40174471C067FDC761A2B791F36F1A3C322734B43DB16880E489BD81",
"Account": "rD8LigXE7165r3VWhSQ4FwzJy7PNrTMwUq",
"Memos": [
{
"Memo": {
"MemoType": "6F666665725F636F6D6D656E74",
"MemoData": "72655F6575722368656467655F726970706C65",
"parsed_memo_type": "offer_comment"
}
}
]
},
"meta": {
"TransactionIndex": 2,
"AffectedNodes": [
{
"CreatedNode": {
"LedgerEntryType": "Offer",
"LedgerIndex": "2069A6F3B349C246630536B3A0D18FECF0B088D6846ED74D56762096B972ADBE",
"NewFields": {
"Sequence": 289444,
"BookDirectory": "D3C7DF102A0CEDB307D6F471B0CE679C5C206D8227D9BB2E5422061A1FB5AF31",
"TakerPays": {
"value": "19.99999999991",
"currency": "EUR",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": {
"value": "20.88367500010602",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"Account": "rD8LigXE7165r3VWhSQ4FwzJy7PNrTMwUq"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "68E8826D6545315B54943AF0D6A45264598F2DE8A71CB9EFA97C9F4456078BE8",
"FinalFields": {
"Flags": 0,
"RootIndex": "68E8826D6545315B54943AF0D6A45264598F2DE8A71CB9EFA97C9F4456078BE8",
"Owner": "rD8LigXE7165r3VWhSQ4FwzJy7PNrTMwUq"
}
}
},
{
"DeletedNode": {
"LedgerEntryType": "Offer",
"LedgerIndex": "9AC6C83397287FDFF4DB7ED6D96DA060CF32ED6593B18C332EEDFE833AE48E1C",
"FinalFields": {
"Flags": 0,
"Sequence": 289443,
"PreviousTxnLgrSeq": 15202438,
"BookNode": "0000000000000000",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "6C1B0818CA470DBD5EFC28FC863862B0DF9D9F659475612446806401C56E3B28",
"BookDirectory": "D3C7DF102A0CEDB307D6F471B0CE679C5C206D8227D9BB2E5422061A1FB5AF31",
"TakerPays": {
"value": "19.99999999991",
"currency": "EUR",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": {
"value": "20.88367500010602",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"Account": "rD8LigXE7165r3VWhSQ4FwzJy7PNrTMwUq"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "D3C7DF102A0CEDB307D6F471B0CE679C5C206D8227D9BB2E5422061A1FB5AF31",
"FinalFields": {
"Flags": 0,
"ExchangeRate": "5422061A1FB5AF31",
"RootIndex": "D3C7DF102A0CEDB307D6F471B0CE679C5C206D8227D9BB2E5422061A1FB5AF31",
"TakerPaysCurrency": "0000000000000000000000004555520000000000",
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3",
"TakerGetsCurrency": "0000000000000000000000005553440000000000",
"TakerGetsIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202438,
"PreviousTxnID": "6C1B0818CA470DBD5EFC28FC863862B0DF9D9F659475612446806401C56E3B28",
"LedgerIndex": "D8614A045CBA0F0081B23FD80CA87E7D08651FA02450C7BEE1B480836F0DC95D",
"PreviousFields": {
"Sequence": 289444,
"Balance": "3712981021"
},
"FinalFields": {
"Flags": 0,
"Sequence": 289445,
"OwnerCount": 13,
"Balance": "3712971021",
"Account": "rD8LigXE7165r3VWhSQ4FwzJy7PNrTMwUq"
}
}
}
],
"TransactionResult": "tesSUCCESS"
}
},
{
"hash": "31b34fd7c90cdc6cf680a814debc6f616c69275c0e99711f904de088a8ed4b28",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "AccountSet",
"Flags": 2147483648,
"Sequence": 387262,
"LastLedgerSequence": 15202440,
"Fee": "10500",
"SigningPubKey": "027DFE042DC2BD07D2E88DD526A5FBF816C831C25CA0BB62A3BF320A3B2BA6DB5C",
"TxnSignature": "30440220572D89688D9F9DB9874CDDDD3EBDCB5808A836982864C81F185FBC54FAD1A7B902202E09AAA6D65EECC9ACDEA7F70D8D2EE024152C7B288FA9E42C427260CF922F58",
"Account": "rn6uAt46Xi6uxA2dRCtqaJyM3aaP6V9WWM"
},
"meta": {
"TransactionIndex": 1,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202429,
"PreviousTxnID": "212D4BFAD4DFB0887B57AB840A8385F31FC2839FFD4169A824280565CC2885C0",
"LedgerIndex": "317481AD6274D399F50E13EF447825DA628197E6262B80642DAE0D8300D77E55",
"PreviousFields": {
"Sequence": 387262,
"Balance": "207020609"
},
"FinalFields": {
"Flags": 0,
"Sequence": 387263,
"OwnerCount": 22,
"Balance": "207010109",
"Account": "rn6uAt46Xi6uxA2dRCtqaJyM3aaP6V9WWM"
}
}
}
],
"TransactionResult": "tesSUCCESS"
}
},
{
"hash": "260bc2964ffe6d81cb25c152f8054ffb2ce6ed04ff89d8d0d0559bc14bef0e46",
"date": "2015-08-12T01:01:10+00:00",
"ledger_index": 15202439,
"tx": {
"TransactionType": "Payment",
"Flags": 2147483648,
"Sequence": 1673,
"LastLedgerSequence": 15202446,
"Amount": "1700000000",
"Fee": "15000",
"SigningPubKey": "02C26CF5D395A1CB352BE10D5AAB73FE27FC0AFAE0BD6121E55D097EBDCF394E11",
"TxnSignature": "304402204190B6DC7D14B1CC8DDAA87F1C01FEDA6D67D598D65E1AA19D4ADE937ED14B720220662EE404438F415AD3335B9FBA1A4C2A5F72AA387740D8A011A8C53346481B1D",
"Account": "rEE77T1E5vEFcEB9zM92jBD3rPs3kPdS1j",
"Destination": "r3AsrDRMNYaKNCofo9a5Us7R66RAzTigiU"
},
"meta": {
"TransactionIndex": 0,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202352,
"PreviousTxnID": "6B3D159578F8E1CEBB268DBC5209ADB35DD075F463855886421D307026D27C67",
"LedgerIndex": "AB5EBD00C6F12DEC32B1687A51948ADF07DC2ABDD7485E9665DCE5268039B461",
"PreviousFields": {
"Balance": "23493344926"
},
"FinalFields": {
"Flags": 0,
"Sequence": 1775,
"OwnerCount": 0,
"Balance": "25193344926",
"Account": "r3AsrDRMNYaKNCofo9a5Us7R66RAzTigiU"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 15202236,
"PreviousTxnID": "A2C23A20377BA7A90F77F01F8E337B64E22C929C5490E2E9698A7A9BFFEC592A",
"LedgerIndex": "C67232D5308CBE1A8C3D75284D98CC1623D906DB30774C06B3F4934BC1DE5CEE",
"PreviousFields": {
"Sequence": 1673,
"Balance": "17034504878"
},
"FinalFields": {
"Flags": 0,
"Sequence": 1674,
"OwnerCount": 0,
"Balance": "15334489878",
"Account": "rEE77T1E5vEFcEB9zM92jBD3rPs3kPdS1j"
}
}
}
],
"TransactionResult": "tesSUCCESS"
}
}
]

View File

@@ -0,0 +1,14 @@
{
"accepted": true,
"stateHash": "D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44",
"closeTime": 492656470,
"parentCloseTime": 492656460,
"closeFlags": 0,
"closeTimeHuman": "2015-Aug-12 01:01:10",
"closeTimeResolution": 10,
"closed": true,
"ledgerVersion": 15202439,
"parentLedgerHash": "12724A65B030C15A1573AA28B1BBB5DF3DA4589AA3623675A31CAE69B23B1C4E",
"totalDrops": "99998831688050493",
"transactionHash": "325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C8"
}

View File

@@ -0,0 +1,17 @@
{
"source": {
"address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J",
"currencies": [
{
"currency": "USD"
}
]
},
"destination": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"value": "1000002",
"currency": "USD"
}
}
}

View File

@@ -0,0 +1,12 @@
{
"source": {
"address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J"
},
"destination": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"value": "1000002",
"currency": "USD"
}
}
}

Some files were not shown because too many files have changed in this diff Show More