Compare commits

...

358 Commits

Author SHA1 Message Date
Geert Weening
8d50034265 Bump version to 0.13.0-rc1 2015-07-22 12:02:15 -07:00
Geert Weening
64f451e904 Merge branch 'release' into develop
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-07-22 11:59:36 -07:00
Geert Weening
b1b47d7d91 Bump version to 0.12.5 2015-07-22 11:53:13 -07:00
Geert Weening
a93c580c92 Bump version to 0.12.5-rc3 2015-07-22 11:49:08 -07:00
Geert Weening
030e2786d1 Update release notes 2015-07-22 11:48:34 -07:00
Geert Weening
56bbe1d437 Update shrinkwrap 2015-07-22 11:42:34 -07:00
Ivan Tivonenko
1fff5ea6dc [FIX] fix AutobridgeCalculator (RT-3445)
pass issuers to AutobridgeCalculator so
it can create offers with right issuers
2015-07-22 11:28:10 -07:00
Ivan Tivonenko
ad9956375f [FIX] change var to let and const 2015-07-22 11:28:00 -07:00
Chris Clark
4787e5d29a Merge pull request #444 from darkdarkdragon/develop-RLJS-370-6
cover api/transaction/order.js with tests
2015-07-21 18:26:36 -07:00
Ivan Tivonenko
194f76d57f cover api/transaction/order.js with tests 2015-07-22 04:18:35 +03:00
Chris Clark
d2ee5cb0bc Merge pull request #443 from darkdarkdragon/develop-RLJS-370-5
cover src/api/transaction/payment.js with unit tests
2015-07-21 17:50:05 -07:00
Ivan Tivonenko
040298db2c cover src/api/transaction/payment.js with unit tests 2015-07-22 03:29:30 +03:00
Chris Clark
b4e6d4b98c Merge pull request #442 from reedrosenbluth/proxy
check if proxy option is undefined, not if it exists
2015-07-21 17:11:42 -07:00
Reed Rosenbluth
a2b31e2677 check if proxy option is undefined, not if it exists 2015-07-21 17:01:02 -07:00
Chris Clark
e997c44a18 Merge pull request #440 from darkdarkdragon/develop-RLJS-370-4
increase test coverage of Account object
2015-07-21 11:37:53 -07:00
Alan Cohen
b27011fb38 Merge pull request #441 from lumberj/add-flow-to-api
Add flow to api
2015-07-21 11:22:47 -07:00
Alan Cohen
b477eb238b Add flow-bin to dev dependencies and ignore it in flowconfig
Add flow to api/server/server.js

Add flow check to src/api: index, accountinfo, balances, orderbook..

orders, settings, transaction, transactions

Add flow typecheck to api/ledger/pathfind

Use eslint plugin flowtype to allow flow type annotations

Babel-eslint emits errors when using the type keyword. This plugin works
around the issue by stripping flow annotations before linting.

Source: https://www.npmjs.com/package/eslint-plugin-flowtype

Add flow to ledger/parse/account-order, trustline, orderbook-order
2015-07-21 10:59:49 -07:00
Ivan Tivonenko
02bc256225 increase test coverage of Account object 2015-07-21 09:19:25 +03:00
sublimator
9f7293127c Merge pull request #439 from clark800/cleanup
Cleanup
2015-07-21 10:38:57 +07:00
sublimator
61a0ea7eac Merge pull request #437 from clark800/naming
Rename "incoming"/"outgoing" to "initiated"
2015-07-21 10:37:40 +07:00
sublimator
51ef93e23b Merge pull request #435 from clark800/consistency
Don't show flags that are set to false
2015-07-21 09:06:05 +07:00
Chris Clark
ab11220e27 Merge pull request #438 from reedrosenbluth/proxy
Proxy Support
2015-07-20 18:22:59 -07:00
Reed Rosenbluth
b23d588747 disable eslint for lines 538 - 548 2015-07-20 18:18:45 -07:00
Reed Rosenbluth
a71dc28523 updated shrinkwrap 2015-07-20 18:02:42 -07:00
Reed Rosenbluth
2de0e13ec5 proxy support 2015-07-20 17:39:46 -07:00
Chris Clark
d0c922ed13 Require uppercase hex for currency codes 2015-07-20 17:34:03 -07:00
Chris Clark
8c82ebec79 Use "core" instead of "utils.core" 2015-07-20 17:33:19 -07:00
Chris Clark
ddd83d10fa Don't show flags that are set to false 2015-07-20 16:52:43 -07:00
Chris Clark
031375e701 Rename "incoming"/"outgoing" to "initiated" 2015-07-20 14:59:59 -07:00
sublimator
4bc285313c Merge pull request #434 from clark800/fixes
Simplify getServerInfo response
2015-07-21 01:21:16 +07:00
Chris Clark
f42dd69b53 Simplify getServerInfo response 2015-07-20 11:15:54 -07:00
sublimator
1dae06fdd8 Merge pull request #433 from clark800/mock-fixtures
Organize rippled mock fixtures
2015-07-20 14:26:17 +07:00
Chris Clark
ff29247b9e Organize rippled mock fixtures 2015-07-17 15:36:37 -07:00
Chris Clark
d9eca203ed Merge pull request #431 from darkdarkdragon/develop-RLJS-370-2
test parseTrustline in getTransaction
2015-07-17 14:22:40 -07:00
Chris Clark
4a85182a79 Merge pull request #432 from darkdarkdragon/develop-RLJS-370-3
testing - add memo field into payment transaction so parsePaymentMemo…
2015-07-17 14:21:46 -07:00
Ivan Tivonenko
055d275f0a test parseTrustline in getTransaction 2015-07-18 00:18:35 +03:00
Ivan Tivonenko
eb7bbe5715 testing - add memo field into payment transaction so parsePaymentMemos from parsePayment is tested 2015-07-18 00:13:31 +03:00
Chris Clark
8d5752883f Merge pull request #430 from clark800/fixtures
Organize fixtures
2015-07-16 15:29:59 -07:00
Chris Clark
2b5f356de8 Organize fixtures 2015-07-16 15:02:27 -07:00
Chris Clark
c9610900fd Merge pull request #429 from darkdarkdragon/develop-RLJS-370-1
add test for getting order and order cancellation transations
2015-07-16 13:31:15 -07:00
Ivan Tivonenko
79b1a65a7b add test for getting order and order cancellation transations 2015-07-16 23:08:57 +03:00
Chris Clark
df21b9453f Merge pull request #428 from darkdarkdragon/develop-fix-transaction-parse
fix attachTransactionDate in getTransaction
2015-07-16 10:25:42 -07:00
Ivan Tivonenko
22257bdd2b fix attachTransactionDate in getTransaction 2015-07-16 05:23:56 +03:00
sublimator
aa3767b180 Merge pull request #427 from clark800/account-info
Add getAccountInfo method and move sequence field in response
2015-07-16 09:48:02 +08:00
Chris Clark
c073c2b7de Add getAccountInfo method and unit test 2015-07-15 17:13:14 -07:00
Chris Clark
529a55efb0 Remove sequence from AccountFields 2015-07-15 15:49:05 -07:00
Chris Clark
c36567e062 Merge pull request #426 from clark800/cleanup
Fix hash128 schema
2015-07-15 15:19:02 -07:00
Chris Clark
f7873f3b41 Fix hash128 schema 2015-07-15 15:15:31 -07:00
Chris Clark
6b54f1c1eb Merge pull request #423 from clark800/cleanup
Cleanup RippleAPI
2015-07-15 15:06:52 -07:00
Chris Clark
1fb1bc7404 Cleanup RippleAPI 2015-07-15 14:55:24 -07:00
Chris Clark
aa646a3acf Merge pull request #421 from clark800/bugfix
Fix parsing of settings transactions
2015-07-15 14:54:25 -07:00
Chris Clark
18ac8a9d03 Fix parsing of settings transactions 2015-07-15 14:43:04 -07:00
Chris Clark
7c357c5d52 Merge pull request #424 from clark800/disable-resubmit
Disable automatic transaction resubmission for RippleAPI
2015-07-15 14:24:18 -07:00
Chris Clark
816db9e0dc Merge pull request #425 from clark800/tx-hash
Add transaction id to response of getTransaction and getTransactions
2015-07-15 14:22:08 -07:00
Chris Clark
fa9e1de4df Add transaction id to response of getTransaction and getTransactions 2015-07-15 13:57:00 -07:00
Chris Clark
137d947606 Disable automatic transaction resubmission for RippleAPI 2015-07-15 13:45:27 -07:00
sublimator
77a05c1881 2000 2015-07-15 19:49:39 +08:00
sublimator
ab13e3fe18 Merge pull request #422 from darentuzi/patch-1
Ternary operator syntax fix
2015-07-15 19:47:22 +08:00
darentuzi
fc4b085026 Ternary operator syntax fix
let to const and syntax update
2015-07-15 14:44:41 +03:00
Chris Clark
67e8f6ab65 Merge pull request #420 from darkdarkdragon/develop-RLJS-408
Test all methods of RippleApi in integration-test
2015-07-14 17:21:37 -07:00
Ivan Tivonenko
792b30c8b5 Test all methods of RippleApi in integration-test 2015-07-15 03:17:39 +03:00
sublimator
3407aadfb8 Merge pull request #418 from clark800/ledger-gaps
Refine ledger gap detection to reduce false positives
2015-07-15 07:21:09 +08:00
sublimator
29e59ebfd2 Merge pull request #419 from clark800/set-regular-key
Add support for regularKey in prepareSettings
2015-07-14 15:29:46 +08:00
Chris Clark
286a684963 Add support for regularKey in prepareSettings 2015-07-13 16:46:57 -07:00
Chris Clark
3ef586d2d3 Refine ledger gap detection to reduce false positives 2015-07-13 15:57:50 -07:00
sublimator
5889037a71 Merge pull request #414 from clark800/merge-orderbook
Merge flipped orderbook in getOrderbook
2015-07-14 06:54:54 +08:00
sublimator
5e3f3969f5 Merge pull request #403 from clark800/no-limit
Get all results when limit is not specified
2015-07-14 06:52:20 +08:00
Chris Clark
eecf45918b Merge pull request #415 from darkdarkdragon/develop-RLJS-404
Port integration tests from ripple-rest
2015-07-13 15:50:54 -07:00
Chris Clark
6d91e6e6b2 Merge pull request #417 from darkdarkdragon/develop-fix-gettransaction-missing-history
throw MissingLedgerHistoryError in getTransaction only when ledgers a…
2015-07-13 15:43:06 -07:00
Ivan Tivonenko
7808f87060 Throw MissingLedgerHistoryError in getTransaction only when ledgers are missing prior to the last closed ledger. 2015-07-14 01:25:07 +03:00
Ivan Tivonenko
6c48b22eea Port integration tests from ripple-rest 2015-07-14 01:10:42 +03:00
Chris Clark
7344f1178b Merge flipped orderbook in getOrderbook 2015-07-13 14:09:33 -07:00
Chris Clark
5a6a3ce6e0 Merge pull request #413 from darkdarkdragon/develop-fix-gettransaction
[FIX] fix getTransaction
2015-07-13 11:06:59 -07:00
Chris Clark
f3a54bf02a Get all results when limit is not specified 2015-07-13 10:30:51 -07:00
Ivan Tivonenko
766dc5d0ce [FIX] fix getTransaction
Do not try to parse transaction if there was error

Throw NotFoundError instead of ApiError in case of ledger_index not defined.
2015-07-11 15:30:41 +03:00
wltsmrz
f8f196903a Merge pull request #406 from wltsmrz/add-resubmission-switch
Add switch to disable transaction resubmission
2015-07-11 02:22:30 +08:00
sublimator
337c41fe10 Merge pull request #400 from clark800/refactor
Refactor to make quality adjustment for XRP more clear
2015-07-11 01:48:35 +08:00
Chris Clark
fb8eefd00d Refactor to make quality adjustment for XRP more clear 2015-07-10 10:24:52 -07:00
wltsmrz
3082b959f6 Add switch to disable transaction resubmission
* Add Transaction.setResumittable(Boolean)
* Add Transaction.isResumittable() -> Boolean
* Add automatic_resubmission (Boolean) config option to Remote
2015-07-10 09:53:31 -07:00
wltsmrz
487371737c Merge pull request #408 from wilsonianb/subscribe-vals
Handle validations subscription messages
2015-07-11 00:51:36 +08:00
Chris Clark
14ccc9359d Merge pull request #410 from clark800/range-check
Check for ledger gaps in getTransactions
2015-07-09 15:19:33 -07:00
Chris Clark
665b76271c Merge pull request #411 from clark800/get-paths
Rename getPathFind to getPaths
2015-07-09 15:15:49 -07:00
Chris Clark
11ca00bb45 Check for ledger gaps in getTransactions 2015-07-09 15:14:59 -07:00
Chris Clark
046d397dfb Rename getPathFind to getPaths 2015-07-09 15:12:45 -07:00
wilsonianb
58cc01b6d3 Handle validations subscription messages 2015-07-09 15:08:39 -07:00
Chris Clark
6a3eb7b9b9 Merge pull request #409 from clark800/counterparty
Add counterparty option to getTransactions
2015-07-09 14:59:10 -07:00
Chris Clark
935a463d3b Merge pull request #407 from clark800/rangeset
Add support for efficient range checking to RangeSet
2015-07-09 13:57:47 -07:00
Chris Clark
f2f4173d7b Add support for efficient range checking to RangeSet 2015-07-09 13:51:27 -07:00
Chris Clark
941aaf6d8b Add counterparty option to getTransactions 2015-07-09 13:46:12 -07:00
Chris Clark
1068b68568 Merge pull request #404 from clark800/options-schemas
Add schemas for options for each ledger request
2015-07-08 16:59:50 -07:00
Chris Clark
e76b9a9051 Add schemas for options for each ledger request 2015-07-08 16:49:47 -07:00
Chris Clark
410ac117f6 Merge pull request #402 from clark800/get-ledger-version
Add ledgerVersion option to getSettings
2015-07-08 16:23:45 -07:00
Chris Clark
fa89c4dee8 Add ledgerVersion option to getSettings 2015-07-08 14:48:29 -07:00
Chris Clark
a464ca2368 Merge pull request #401 from clark800/get-ledger-version
Add getLedgerVersion and unit test
2015-07-08 14:21:16 -07:00
Chris Clark
befd89c3d7 Add getLedgerVersion and unit test 2015-07-08 14:05:25 -07:00
Chris Clark
49640cf282 Merge pull request #384 from mwshortt/IOU-suffix-removal
Decouple the Amount class from math performed on values
2015-07-08 12:14:05 -07:00
Madeline Shortt
2ac4549712 Rename IOUValue.js and XRPValue.js to lowercase and other fixes 2015-07-08 11:49:15 -07:00
Madeline Shortt
6bffe06c3b Change this._value to be an instance of IOU or XRP Value 2015-07-08 11:49:14 -07:00
Madeline Shortt
13e9ad45f9 Remove BigNumber completely from Amount and small changes 2015-07-08 11:49:13 -07:00
Madeline Shortt
193fcc9014 Create separate Value classes to decouple currency math and the Amount class 2015-07-08 10:25:45 -07:00
Madeline Shortt
30529b7a04 Remove IOU_SUFFIX from orderbookutils.js and clean up serializedtypes.js 2015-07-08 10:25:45 -07:00
sublimator
38b254e7f0 Merge pull request #399 from clark800/rename
Rename getAccountTransactions to getTransactions
2015-07-08 09:08:16 +07:00
sublimator
c4b98d2139 Merge pull request #398 from clark800/orders
Add makerExchangeRate (quality) to orders, and maker to account orders
2015-07-08 08:55:41 +07:00
Chris Clark
1309b58592 Add makerExchangeRate (quality) to orders, and maker to account orders 2015-07-07 18:28:37 -07:00
Chris Clark
9b9d2dc32b Rename getAccountTransactions to getTransactions 2015-07-07 17:52:28 -07:00
Chris Clark
73020fb8ae Merge pull request #397 from lukeburns/develop
Added issuer to payment example in guide
2015-07-07 14:00:02 -07:00
sublimator
8221db833f Merge pull request #391 from clark800/get-pathfind
Convert getPathFind and add unit test
2015-07-07 13:16:16 +07:00
Chris Clark
09b10d3f2c Convert getPathFind and add unit test 2015-07-06 16:11:23 -07:00
Luke Burns
c31798c7f8 changed currency to USD in submit payment example 2015-07-03 00:43:46 -04:00
Luke Burns
064c5e9e50 added issuer to submit payment example 2015-07-02 23:21:19 -04:00
Chris Clark
077a53475d Merge pull request #392 from clark800/server
Convert server methods, add unit tests, and cleanup
2015-07-02 11:23:29 -07:00
Chris Clark
32ca23a00b Convert server methods, add unit tests, and cleanup 2015-07-02 11:17:06 -07:00
Chris Clark
1a7cdd7d04 Merge pull request #396 from sublimator/eslint-update
Use parser: babel-eslint which supports facebook flow
2015-07-02 10:13:43 -07:00
Nicholas Dudfield
85befa467b Use parser: babel-eslint which supports facebook flow 2015-07-02 18:30:11 +07:00
wltsmrz
9b956e57ac Merge pull request #394 from sublimator/eslint-update
Update eslint
2015-07-02 18:56:40 +08:00
sublimator
93c0c49002 Merge pull request #390 from wltsmrz/refactor-api-deprecation
Refactor API deprecation, fix request constructors with positional args
2015-07-02 17:51:28 +07:00
wltsmrz
44954621e0 Refactor API deprecation, fix request constructors with positional args 2015-07-02 03:46:55 -07:00
Nicholas Dudfield
96e5d484da Update eslint 2015-07-02 17:44:38 +07:00
sublimator
4efe0b920e Merge pull request #388 from clark800/get-orderbook
Convert getOrderBook and add unit test
2015-07-01 12:43:00 +07:00
Chris Clark
d158cc7fc1 Fix other indentation problems 2015-06-30 16:53:48 -07:00
Chris Clark
034cd4eaf6 Fix indentation 2015-06-30 16:36:14 -07:00
Chris Clark
d5f3d90486 Add flipped orderbook to getOrderBook response 2015-06-30 15:43:30 -07:00
Chris Clark
99e076b7dd Sort orderbook orders by price 2015-06-30 15:21:28 -07:00
Chris Clark
8f9aec83f2 Fix counterparty/issuer bug 2015-06-30 13:02:42 -07:00
Chris Clark
87a8745a64 Fix unit tests 2015-06-30 12:52:21 -07:00
Chris Clark
2ab51d7a8a Revert remote.js 2015-06-30 12:12:21 -07:00
Chris Clark
3e0f43e44e Convert getOrderBook and add unit test 2015-06-26 16:27:34 -07:00
Chris Clark
6a763fab18 Merge pull request #385 from clark800/get-orders
Convert getOrders and add unit test
2015-06-26 10:46:41 -07:00
Chris Clark
3fe6726789 Merge pull request #386 from sublimator/rm-message
Remove message.js
2015-06-26 10:30:04 -07:00
Nicholas Dudfield
5b4deabd90 Remove message.js 2015-06-26 15:33:26 +07:00
Chris Clark
84bc7dd4aa Convert getOrders and add unit test 2015-06-25 14:21:39 -07:00
Chris Clark
3960b4e11f Merge pull request #383 from clark800/remove
Add unit test for generateWallet and reorganize API files
2015-06-25 11:43:39 -07:00
Chris Clark
141215fc38 Add unit test for getSettings 2015-06-25 11:23:56 -07:00
Chris Clark
2446e2f6da Add unit test for generateWallet and reorganize API files 2015-06-24 18:03:03 -07:00
Chris Clark
81a9bc0739 Merge pull request #382 from clark800/notifications
Merge notifications functionality into getAccountTransactions
2015-06-24 16:44:53 -07:00
Chris Clark
64e86f403e Merge notifications functionality into getAccountTransactions 2015-06-24 16:14:28 -07:00
Chris Clark
77f1351e5b Merge pull request #381 from clark800/balances
Update getBalances to use getTrustlines
2015-06-24 12:53:58 -07:00
Chris Clark
47a87f3a92 Update getBalances to use getTrustlines 2015-06-24 12:38:13 -07:00
Chris Clark
d2df75dc25 Merge pull request #380 from clark800/trustlines
Add getTrustlines and unit test
2015-06-24 11:36:34 -07:00
Chris Clark
d92fbfb7aa Add getTrustlines and unit test 2015-06-24 11:17:46 -07:00
wltsmrz
1b3be55711 Merge pull request #378 from sublimator/signing-data
Add Transaction.signingData
2015-06-23 20:58:21 +08:00
Nicholas Dudfield
8d98e443c5 Add Transaction.signingData() 2015-06-23 19:52:41 +07:00
Nicholas Dudfield
46121edd62 Remove global install for CI dependencies:
* Add them to package.json
* Fixes weird error with missing `esprima-fb`
2015-06-23 19:47:05 +07:00
sublimator
1a32536ac8 Merge pull request #355 from ripple/validate-uint-hex-input
Validate UInt* hex input
2015-06-22 13:59:17 +07:00
Chris Clark
85bf6891f9 Merge pull request #374 from clark800/empty-strings
Remove empty counterparties in XRP amounts
2015-06-18 17:39:18 -07:00
Chris Clark
1ccca1c4ec Remove empty counterparties in XRP amounts 2015-06-18 17:36:45 -07:00
Geert Weening
22cd70e53f Merge pull request #373 from clark800/get-acct-tx
Convert getAccountTransactions
2015-06-18 17:29:38 -07:00
Chris Clark
d8aad1444e Merge pull request #375 from clark800/flow-check
Use "flow check" to prevent "out of retries" errors
2015-06-18 15:12:56 -07:00
Chris Clark
621dfd9ca5 Use "flow check" to prevent "out of retries" errors 2015-06-18 15:10:16 -07:00
wltsmrz
2ad6a1a77e Validate UInt hex input 2015-06-18 13:50:11 -07:00
Chris Clark
8f37438a08 Convert getAccountTransactions 2015-06-17 18:04:24 -07:00
Chris Clark
ff6ac0333c Merge pull request #369 from clark800/parse-tx
Rewrite transaction parser and add unit test for getTransaction
2015-06-16 16:55:15 -07:00
Chris Clark
1b936d2aa2 Rewrite transaction parser and add unit test for getTransaction 2015-06-16 16:24:21 -07:00
sublimator
df0cff969c Merge pull request #372 from clark800/license
Add license to package.json
2015-06-16 18:33:49 +07:00
Chris Clark
46e2598499 Add license to package.json 2015-06-15 17:28:38 -07:00
Chris Clark
0b32378ab5 Merge pull request #364 from clark800/submit-test
Add unit test for submit
2015-06-15 14:39:19 -07:00
Chris Clark
bf25eb350f Display git log in travis output 2015-06-15 14:32:21 -07:00
Chris Clark
97cea2ce4d Add unit test for submit 2015-06-12 16:54:38 -07:00
Chris Clark
82ed402b16 Merge pull request #370 from clark800/fix-perf-test
Fix perf test
2015-06-12 16:28:24 -07:00
Chris Clark
ca7b69a2a0 Fix perf test 2015-06-12 16:22:05 -07:00
Chris Clark
d9c61a9431 Merge pull request #366 from darkdarkdragon/develop-RT-3462
[FIX] emit empty offers list (RT-3462)
2015-06-10 11:38:25 -07:00
Ivan Tivonenko
3b636ce2d1 [FIX] emit empty offers list (RT-3462)
For autobridged order books, emit 'model' event,
even if offers is empty, to indicate that
orderbook is loaded
2015-06-10 21:10:18 +03:00
wltsmrz
2196352335 Merge pull request #363 from clark800/parallel
Parallelize CircleCI tests
2015-06-10 05:06:11 +08:00
Chris Clark
823d7048ba Parallelize CircleCI tests 2015-06-09 13:13:13 -07:00
Chris Clark
d066e1145d Merge pull request #361 from clark800/prepare-trustline
Add unit tests for prepareTrustline, prepareOrderCancellation, and sign
2015-06-09 13:12:24 -07:00
Chris Clark
fb7021abcc Add unit tests for prepareTrustline, prepareOrderCancellation, and sign 2015-06-09 11:30:51 -07:00
Chris Clark
9c14fb2379 Merge pull request #360 from clark800/prepare-order
Add unit test for prepareOrder and fix bugs
2015-06-08 14:36:13 -07:00
Chris Clark
a114bf42c4 Add unit test for prepareOrder and fix bugs 2015-06-08 14:11:35 -07:00
Chris Clark
87b6c09de3 Merge pull request #358 from clark800/flow-type
Add flow type checking
2015-06-08 14:08:07 -07:00
Chris Clark
f1c95112bd Add circle.yml file 2015-06-08 14:00:08 -07:00
Chris Clark
76d8c8b061 Add flow type checking 2015-06-08 14:00:08 -07:00
Chris Clark
7cbcb9a220 Merge pull request #359 from darkdarkdragon/develop-RT-3445
[FIX] fix AutobridgeCalculator (RT-3445)
2015-06-08 11:08:44 -07:00
Ivan Tivonenko
4433ac57bd [FIX] change var to let and const 2015-06-08 20:38:22 +03:00
Ivan Tivonenko
5379da4874 [FIX] fix AutobridgeCalculator (RT-3445)
pass issuers to AutobridgeCalculator so
it can create offers with right issuers
2015-06-07 01:35:49 +03:00
Chris Clark
d488ce55b3 Merge pull request #357 from clark800/v2-schemas
Update schemas, use schemas for validation, and switch to is-my-json-valid
2015-06-05 15:25:32 -07:00
Chris Clark
908e306f04 Update schemas, use schemas for validation, and switch to is-my-json-valid 2015-06-05 14:58:23 -07:00
Chris Clark
62b5953abe Merge pull request #356 from sublimator/remove-sjcl-custom
Remove sjcl-custom in favor of sjcl-extended module in separate repo
2015-06-04 11:50:10 -07:00
Nicholas Dudfield
35ae968d9f Remove sjcl-custom in favor of sjcl-extended module in separate repo 2015-06-04 16:19:31 +07:00
Chris Clark
22dc39b920 Merge pull request #354 from clark800/v2
Move ripple-rest/api into src/api, exposing RippleAPI
2015-05-29 11:12:39 -07:00
Chris Clark
278331cc4a Move ripple-rest/api into src/api, exposing RippleAPI 2015-05-29 10:49:14 -07:00
wltsmrz
16e3541a10 Merge pull request #353 from clark800/develop
Fix bug in trace message for updateOwnerOffersFundedAmount
2015-05-28 13:54:43 -07:00
Chris Clark
7d7970d318 Fix bug in trace message for updateOwnerOffersFundedAmount 2015-05-27 11:56:27 -07:00
wltsmrz
f4fa10b9c0 Merge pull request #351 from clark800/sjcl-update
Update sjcl and delete custom ripemd160 and montgomery
2015-05-26 15:05:46 -07:00
Chris Clark
e68096bd27 Fix lint errors in sjcl-ecdsa-recoverablepublickey.js 2015-05-26 14:42:30 -07:00
Chris Clark
50cda426eb Update sjcl and delete custom ripemd160, montgomery, and jacobi 2015-05-26 14:42:18 -07:00
Chris Clark
1038421428 Merge pull request #350 from fhinkel/develop
Add missing semicolon in example
2015-05-26 13:56:44 -07:00
Franziska Hinkelmann
a5046ab086 Add missing semicolon in example 2015-05-24 11:40:43 +02:00
Geert Weening
ebbec1954e Merge pull request #348 from clark800/fix-orderbook-crash
Prevent crash when listening for "model" events on the OrderBook clas…
2015-05-21 12:07:47 -07:00
Chris Clark
4021018931 Fix lint errors in orderbook.js 2015-05-21 10:07:24 -07:00
Chris Clark
5824c3cb7c Prevent crash when listening for "model" events on the OrderBook class and an issuer creates an order with one of their issues 2015-05-21 09:53:55 -07:00
Geert Weening
45a1b9471e Merge pull request #328 from ripple/fix-remote-error-log
Fix error logging on Remote for object-type messages
2015-05-21 09:07:48 -07:00
wltsmrz
70bc819665 Lint 2015-05-21 04:17:11 -07:00
sublimator
e05f3e9b9c Merge pull request #343 from ripple/fix-serializedobject-append
Fix serializedobject append for excessively large bytes length
2015-05-21 10:35:20 +07:00
wltsmrz
e93f1ab6f4 Fix serializedobject append for excessively large bytes length 2015-05-20 20:18:17 -07:00
wltsmrz
0878a8ecf0 Fix error logging on Remote
* JSON messages are now parsed in server.js
* Remote._handleMessage expects all messages to be objects rather
  than objects or strings
* Invalid responses from the server will now result in a log
  message that is not entirely vague
* Added Log.setEngine/getEngine. Use Log.setEngine(Log.engines.none)
  to silence logging
2015-05-20 20:08:32 -07:00
sublimator
6ea07139dc Merge pull request #349 from ripple/check-unknown-serialization-fields
Check unknown serialization fields
2015-05-21 09:14:17 +07:00
wltsmrz
55fca2d7d5 Lint 2015-05-20 18:47:05 -07:00
wltsmrz
8596dcef21 Check unknown serialization fields 2015-05-20 18:09:02 -07:00
Geert Weening
bca84d5508 Merge pull request #338 from ripple/fix-tx-summary
Fix transaction summary for transactions that fail format validation
2015-05-20 16:11:27 -07:00
wltsmrz
65f7485497 Lint 2015-05-20 05:00:08 -07:00
wltsmrz
457b02c781 Merge pull request #345 from sublimator/es6-lints
Fix eslint issues
2015-05-20 04:40:43 -07:00
Nicholas Dudfield
c8e0fa85f3 Fix eslint issues. 2015-05-20 16:04:04 +07:00
wltsmrz
5e714f6143 Fix transaction summary for transactions that fail with remoteError 2015-05-15 22:41:29 -07:00
Chris Clark
4ecbf31898 Merge pull request #344 from clark800/babel
Setup babel transpiler
2015-05-15 16:59:32 -07:00
Chris Clark
ea24bf0415 Fix lint errors 2015-05-15 16:45:24 -07:00
Chris Clark
171f8349cb Fix requires in bin directory 2015-05-15 16:34:50 -07:00
Chris Clark
398f8d001f Set up babel transpiler 2015-05-14 16:07:47 -07:00
Chris Clark
e66978fb48 Merge pull request #342 from clark800/remove-cpp
Remove src/cpp and clean up files
2015-05-13 17:13:44 -07:00
Chris Clark
c57d528db7 Fix lint errors in remote-test.js 2015-05-13 16:37:46 -07:00
Chris Clark
0c47310063 Prevent lint checking of moved files 2015-05-13 15:21:20 -07:00
Chris Clark
cb4f6e37a8 Remove src/cpp and clean up files 2015-05-13 15:03:32 -07:00
Geert Weening
ab943f36c3 Merge pull request #337 from clark800/sjcl_npm
Use sjcl npm module, delete sjcl code
2015-05-12 16:14:59 -07:00
Chris Clark
de7fc78ef0 Remove lint errors in sjcl-custom 2015-05-12 15:00:07 -07:00
Chris Clark
9a502580fd Use sjcl npm module, delete sjcl code 2015-05-12 15:00:02 -07:00
Chris Clark
d56e70b995 Merge pull request #340 from clark800/remove-return-value
Remove return value of mergeDirectAndAutobridgedBooks
2015-05-11 16:51:55 -07:00
Chris Clark
e9aaf50d59 Remove return value of mergeDirectAndAutobridgedBooks 2015-05-11 10:49:13 -07:00
Geert Weening
ac0a4f521a Bump version to 0.12.5-rc2 2015-05-05 11:20:08 -07:00
Geert Weening
a3380c5cdd Update release notes 2015-05-05 11:15:31 -07:00
Geert Weening
6a6d2a0787 Merge pull request #339 from boxbag/autobridging-empty-orders
[TASK] add short circuit when there are no direct nor autobridged offers
2015-05-05 09:52:42 -07:00
Bo Chen
64809d9ae2 [TASK] add short circuit when there are no direct nor autobridged offers 2015-05-04 13:11:54 -07:00
Chris Clark
d14b38bc91 Merge pull request #335 from clark800/remove-float
Remove float.js and wallet.js
2015-05-01 13:31:17 -07:00
Chris Clark
440dfb5785 Merge pull request #334 from clark800/remove-config
Remove config singleton (global state)
2015-05-01 13:30:36 -07:00
Chris Clark
d4a4b5f4fb Remove float.js and wallet.js 2015-04-30 18:47:43 -07:00
Chris Clark
0c000a7fee Fix lint errors in amount-test.js 2015-04-30 18:31:59 -07:00
Chris Clark
c655c2a20e Remove config singleton (global state) 2015-04-30 18:30:53 -07:00
Geert Weening
3ba5a18b91 Bump version to 0.12.5-rc1 2015-04-29 11:54:36 -07:00
Geert Weening
bdb3415855 Update release notes 2015-04-29 11:54:05 -07:00
Geert Weening
5ef5bdd9d9 Merge pull request #332 from geertweening/develop
Add offer autobridging
2015-04-29 11:47:05 -07:00
Bo Chen
c7bbce8371 [FEATURE] add offer autobridging 2015-04-29 11:28:14 -07:00
Geert Weening
5e2c26a4a2 Merge branch 'release'
Conflicts:
	npm-shrinkwrap.json
	package.json
2015-04-29 08:50:44 -07:00
Geert Weening
631faa20ec Generate shrinkwrap for node 0.10.38 2015-04-29 08:48:39 -07:00
Geert Weening
2db17ba67c Regenerate shrinkwrap using npm-shrinkwrap
cleans up inconsistencies
2015-04-24 16:22:16 -07:00
Geert Weening
50eca42e35 Bump version to 0.12.4 2015-04-24 16:21:32 -07:00
Geert Weening
f327487157 Update release notes 2015-04-24 16:18:05 -07:00
Geert Weening
85b64b7ac3 Bump version to 0.12.4-rc1 2015-04-24 15:44:08 -07:00
Geert Weening
cf17a9e8d6 Update release notes 2015-04-24 15:44:07 -07:00
Chris Clark
71a1282b89 Remove unused dependency: superagent 2015-04-24 15:44:07 -07:00
Chris Clark
85e1f2f47d Fix lint errors in ledger.js 2015-04-24 15:44:07 -07:00
Chris Clark
1f68eba146 Delete unused crypt.js 2015-04-24 15:44:07 -07:00
Chris Clark
d71873442f Update ripple-wallet-generator version 2015-04-24 15:44:07 -07:00
Chris Clark
10ca2da2d6 Fix lint errors in crypt.js, messages.js, remote.js, message-test.js 2015-04-24 15:44:07 -07:00
Chris Clark
c7ba822320 Improve entropy security and increase CCM tag length 2015-04-24 15:44:06 -07:00
Geert Weening
ef3ce46d00 Bump dependency versions
bignumber 2.0.7
lodash 3.6.0
lru-cache 2.5.2
2015-04-24 15:44:06 -07:00
Geert Weening
c4595e03ce Bump version to 0.12.3 2015-04-24 15:44:06 -07:00
Geert Weening
26a7eb456b Bump version to 0.12.3-rc2 2015-04-24 15:44:06 -07:00
Geert Weening
142a85d6a7 Update release notes 2015-04-24 15:44:06 -07:00
Geert Weening
ef51490a1a Bump version to 0.12.3 2015-04-13 16:27:52 -07:00
Geert Weening
c40d643238 Bump version to 0.12.3-rc2 2015-04-10 14:36:47 -07:00
Geert Weening
6f23c88567 Update release notes 2015-04-10 14:36:15 -07:00
Geert Weening
56958a6242 Merge pull request #324 from clark800/develop
Add Amount.scale
2015-04-10 13:25:57 -07:00
Chris Clark
74dac97b36 Add Amount.scale 2015-04-10 12:56:50 -07:00
Geert Weening
2f2e41c781 Bump version to 0.12.3-rc1 2015-04-08 13:36:23 -07:00
Geert Weening
8c872f71c6 Update release notes 2015-04-08 13:36:01 -07:00
wltsmrz
b40b496866 Merge pull request #322 from clark800/rest1.7
Add getLedgerSequence method
2015-04-08 11:20:46 -07:00
Geert Weening
569fec296e Merge pull request #321 from clark800/paranoia
[FIX] Fail if PRNG has not been seeded with at least 256 bits of entr…
2015-04-08 11:16:47 -07:00
Chris Clark
56d8aa797a Fix lint errors in transaction-test.js and transaction-manager-test.js 2015-04-07 18:08:07 -07:00
Chris Clark
fe7e30b737 [FIX] Fail if PRNG has not been seeded with at least 256 bits of entropy before generating ECDSA signatures 2015-04-07 18:08:02 -07:00
Chris Clark
a114281c60 Update comment about the fee base reference constant 2015-04-07 14:34:26 -07:00
Chris Clark
d09548d04d Add getLedgerSequence 2015-04-07 14:34:22 -07:00
Geert Weening
a02b8e3e5c Merge branch 'release' into develop 2015-04-06 13:00:36 -07:00
Geert Weening
2c3f9ca202 bump version to 0.12.2 2015-04-06 12:57:47 -07:00
Geert Weening
587782820d Merge pull request #318 from sublimator/serial-offender
Fix SerializedObject.append performance issue
2015-04-06 12:54:48 -07:00
Nicholas Dudfield
8fad048569 Update verify_ledger_json.js script:
* Set `Amount.strict_mode = false` in verify_ledger_json.js script
* Don't try and calculate hash of non present accountState
* Fix lint issues
2015-04-04 14:51:35 +07:00
Nicholas Dudfield
f7c35b118e Fix SerializedObject.append perf issue:
* Replace array.concat(array2) with Array.prototype.push.apply
2015-04-04 14:51:12 +07:00
Geert Weening
65a669bbb2 Merge pull request #319 from sublimator/lints
eslint fixes
2015-04-04 00:35:57 -07:00
Nicholas Dudfield
9985acc539 Fix linting issues:
* Fix lint violations in various files
* Use per-file comment directives to make new-cap a warning instead of error
  * sjcl.* don't conform to our standards and eslint exemptions are unweildy
2015-04-04 14:23:41 +07:00
Geert Weening
f1f0a43f21 Bump version to 0.12.1-rc1 2015-03-27 16:56:27 -07:00
Geert Weening
6b856c3cc5 Update release notes 2015-03-27 16:55:44 -07:00
Geert Weening
d92888ed73 Merge pull request #317 from ripple/fix-browser-log
Check that Error.stack is available, fixes logging in browser
2015-03-27 16:52:18 -07:00
wltsmrz
0357840654 Lint 2015-03-27 15:54:26 -07:00
wltsmrz
53cae3a66d Check that stack trace is available, fixes logging in browser 2015-03-27 15:49:13 -07:00
Geert Weening
949a1ca4ae Bump version to 0.12.1 2015-03-26 15:12:14 -07:00
Geert Weening
e667536a5b Bump version to 0.12.1-rc6 2015-03-25 12:15:09 -07:00
Geert Weening
dde000a4bb Update release notes 2015-03-25 12:14:30 -07:00
Geert Weening
aa1f5a8e7d Merge pull request #316 from clark800/fix/to_human_small_number
[FIX] Make Amount.to_human return correct results for very small numbers
2015-03-25 12:09:13 -07:00
Chris Clark
bfbfcc2894 Fix lint errors in amount-test.js 2015-03-25 11:28:13 -07:00
Chris Clark
6abfa759aa [FIX] Make Amount.to_human return correct results for very small numbers 2015-03-25 11:19:25 -07:00
Alan Cohen
7cbac2e757 Merge pull request #315 from ripple/readme-link-fix
Fix link in GUIDES.md
2015-03-24 11:04:31 -07:00
Alan Cohen
1012381d3d Update GUIDES.md 2015-03-24 09:17:43 -07:00
Geert Weening
6de96f62df Merge pull request #314 from lumberj/addflags
[TASK] Add GlobalFreeze and NoFreeze flags
2015-03-23 12:23:05 -07:00
Alan Cohen
e2ed2bdbf6 [TASK] Add GlobalFreeze and NoFreeze flags
See: https://wiki.ripple.com/Ledger_Format#AccountRoot
2015-03-20 14:31:47 -07:00
Geert Weening
e248c54aa5 Merge pull request #312 from clark800/mocha-timeout
Increase timeout for mocha tests because sometimes tests fail on travis ...
2015-03-19 09:09:46 -07:00
Chris Clark
1c9635edad Increase timeout for mocha tests because sometimes tests fail on travis due to taking too long 2015-03-18 17:27:06 -07:00
Geert Weening
25cf6c52e4 Bump version to 0.12.1-rc5 2015-03-18 17:24:24 -07:00
Geert Weening
7859ef6145 Update release notes 2015-03-18 17:23:46 -07:00
Geert Weening
6efaa4ac7e Merge pull request #311 from vhpoet/patch-4
[FIX] Amount: clone in ratio_human, product_human
2015-03-18 17:21:21 -07:00
Vahe Hovhannisyan
19e17a8431 [FIX] Amount: clone in ratio_human, product_human
Amount.ratio_human and Amount.product_human should change and return the cloned Amount object.
2015-03-18 17:09:17 -07:00
Geert Weening
c865ae9734 Bump version to 0.12.1-rc4 2015-03-17 11:19:34 -07:00
Geert Weening
6959f74073 Update release notes 2015-03-17 11:14:36 -07:00
Chris Clark
9f4d21e976 Merge pull request #310 from darkdarkdragon/release-base-decode-fix
[FIX] fix Base:decode
2015-03-17 10:12:07 -07:00
Ivan Tivonenko
719f39c01c [FIX] fix Base:decode
check for invalid input in decoded data
2015-03-17 05:53:02 +02:00
Geert Weening
25bb9c7320 Bump version to 0.12.1-rc3 2015-03-11 11:49:30 -07:00
Geert Weening
a160e16abd Update release notes 2015-03-11 11:48:46 -07:00
Geert Weening
ec31841aa5 Merge pull request #305 from geertweening/add-default-rippling-flag
Add DefaultRipple account flag
2015-03-11 11:41:49 -07:00
wltsmrz
3e249902c4 Add DefaultRipple account flag 2015-03-10 11:44:50 -07:00
Geert Weening
21bb766f06 Update release notes 2015-03-10 11:30:34 -07:00
Geert Weening
a883151400 Bump version to 0.12.1-rc2 2015-03-09 10:06:04 -07:00
Geert Weening
3c7fe82cbd Regenerate shrinkwrap 2015-03-09 10:05:49 -07:00
Geert Weening
899fc09704 Update release notes 2015-03-09 10:03:32 -07:00
Geert Weening
daa45a44b9 Merge pull request #297 from ripple/add-log-info
Add filename and line number to log, use log.warn() for deprecations
2015-03-09 09:45:18 -07:00
Geert Weening
52494628c3 Merge pull request #301 from ripple/amount-strict-mode
Add Amount.strict_mode for toggling range validation
2015-03-06 19:54:58 -08:00
Geert Weening
dbf5d21b72 Merge pull request #303 from clark800/morebasetest
[TASK] Fix issues in base.js and add more tests
2015-03-06 19:53:27 -08:00
wltsmrz
441bd4dfbf Lint remote.js 2015-03-06 19:06:24 -08:00
wltsmrz
8452f05dda Lint log.js 2015-03-06 18:36:27 -08:00
Chris Clark
0d2325e646 [TASK] Fix seed-test.js lint errors 2015-03-06 17:41:42 -08:00
wltsmrz
90329d3d73 Add filename and line number to log, use log.warn() for deprecations 2015-03-06 17:35:24 -08:00
Chris Clark
ca83a142f8 [TASK] Fix issues in base.js and add more tests 2015-03-06 17:33:52 -08:00
wltsmrz
d3b2d3d5c5 Merge pull request #304 from clark800/fix/travis_eslint2
[FIX] Fix bug in .travis.yml generation of list of modified files in pul...
2015-03-06 17:18:55 -08:00
Chris Clark
255177487c [FIX] Fix bug in .travis.yml generation of list of modified files in pull request 2015-03-06 15:53:52 -08:00
wltsmrz
ed0b75bcde Use new Amount(NaN) rather than Amount.NaN() 2015-03-06 15:38:44 -08:00
Chris Clark
06500a7909 Merge pull request #302 from clark800/fix/travis_eslint
[FIX] Don't lint deleted files and pull eslintrc from javascript-style-g...
2015-03-06 13:32:47 -08:00
wltsmrz
6e16bf68ae Lint serializedobject test 2015-03-06 13:01:57 -08:00
Chris Clark
ad22480117 [FIX] Don't lint deleted files and pull eslintrc from javascript-style-guide repo to ensure that it is up to date 2015-03-06 11:20:20 -08:00
wltsmrz
2fcd09072f Lint serializedtypes.js 2015-03-06 01:10:22 -08:00
wltsmrz
f0c785b196 Lint amount.js 2015-03-06 01:06:58 -08:00
Geert Weening
84fe76bada Merge pull request #296 from ripple/fix-currency-parsing
Fix currency parsing of non-alphanumeric and no-currency currencies
2015-03-05 22:32:04 -08:00
wltsmrz
b5ed8f59a7 Add Amount.strict_mode for toggling range validation 2015-03-05 22:21:35 -08:00
Geert Weening
52526f90d7 Merge pull request #298 from clark800/basetest
[TEST] Add unit tests for Base
2015-03-05 13:08:56 -08:00
wltsmrz
99e6e81e65 Merge pull request #299 from boxbag/fix-taker-pays-funded
[FIX] fix taker pays funded calculation
2015-03-05 11:17:43 -08:00
Bo Chen
5af824f5cf [FIX] fix taker pays funded calculation
When calling `parseInt` on a string with scientific notation, it ignores the exponents.
2015-03-05 09:43:27 -08:00
wltsmrz
2166bb2e88 Fix currency parsing of non-alphanumeric and no-currency currencies 2015-03-04 19:10:39 -08:00
Chris Clark
ae884c0200 [TEST] Add unit tests for Base 2015-03-04 13:02:36 -08:00
wltsmrz
423ec7d08a Merge pull request #294 from clark800/baseconverter
Refactor base conversion
2015-03-03 20:23:08 -08:00
Chris Clark
914cd6ecb2 Add unit tests for convertBase 2015-03-03 12:22:42 -08:00
Geert Weening
f221c82859 Merge pull request #293 from boxbag/offer-quality
Offer quality
2015-03-03 10:52:38 -08:00
wltsmrz
d57be723e6 Merge pull request #295 from stevenzeiler/ripple-wallet-version
[TASK] Bump ripple-wallet-generator patch version
2015-03-02 16:58:40 -08:00
Steven Zeiler
777554809a [TASK] Bump ripple-wallet-generator patch version 2015-03-02 16:53:52 -08:00
Chris Clark
f2b63fa4a8 Refactor base conversion 2015-02-27 21:28:23 -08:00
Bo Chen
4d06ce7454 [FIX] fix eslint errors 2015-02-27 16:12:06 -08:00
Bo Chen
8da6ec5fa3 [TASK] reverse skip of order book tests 2015-02-27 09:18:01 -08:00
Bo Chen
2a5a8b498d [FIX] fix handling of quality in order book 2015-02-27 09:18:01 -08:00
Bo Chen
a9b7d7d793 Merge pull request #292 from ripple/revert-286-set-user-agent
Revert "Set User-Agent Header with ripple-lib/{version}"
2015-02-27 09:17:27 -08:00
Bo Chen
6578cf5dd7 Revert "Set User-Agent Header with ripple-lib/{version}" 2015-02-26 14:40:07 -08:00
Geert Weening
2e21e8a43c Merge pull request #290 from ripple/transactionmanager-tests
Add TransactionManager test
2015-02-26 12:48:42 -08:00
Geert Weening
176e1fd9d4 Merge pull request #291 from ripple/update-binary-format
Update binary format
2015-02-26 11:41:34 -08:00
wltsmrz
c3b274b18f Add TransactionManager test 2015-02-25 18:49:59 -08:00
wltsmrz
8e134918fb Update binary format 2015-02-25 18:39:42 -08:00
wltsmrz
2b531d2a1f Disable breaking OrderBook tests 2015-02-25 01:36:12 -08:00
Geert Weening
87317dd54a Merge pull request #289 from ripple/fix-setregularkey-transaction-format
Fix RegularKey requirement in SetRegularKey transaction format
2015-02-24 09:23:48 -08:00
wltsmrz
618548c88d Merge pull request #273 from clark800/feature/eslint
[TASK] Add eslint enforcement to travis.yml
2015-02-23 17:50:41 -08:00
Chris Clark
b62f42006c [TASK] Add eslint enforcement to travis.yml 2015-02-23 16:34:22 -08:00
wltsmrz
c275174f27 Fix RegularKey requirement in SetRegularKey transaction format 2015-02-23 13:09:50 -08:00
Geert Weening
af4ed295e0 Bump version to 0.12.1-rc1 2015-02-23 09:25:42 -08:00
Geert Weening
7614a03ea8 Update release notes 2015-02-23 09:24:13 -08:00
Geert Weening
d9527726b6 Merge pull request #285 from boxbag/orderbook-fix
[FIX] fix order funded amount calculation
2015-02-23 09:16:44 -08:00
Geert Weening
05f4099709 Merge pull request #288 from ripple/fix-setfee-transaction-format
Fix Features field requirement in SetFee transaction format
2015-02-23 09:16:13 -08:00
wltsmrz
a20a649013 Fix Features field requirement in SetFee transaction format 2015-02-20 15:01:05 -08:00
Geert Weening
0e3e64105c Merge pull request #287 from clark800/bignumber_update
[TASK] Update bignumber.js and use new feature to simplify our code
2015-02-20 14:50:59 -08:00
Bo Chen
b2cdb1a6ae [FIX] fix order funded amount calculation 2015-02-20 14:49:47 -08:00
Alan Cohen
812432db96 Merge pull request #286 from lumberj/set-user-agent
Set User-Agent Header with ripple-lib/{version}
2015-02-20 10:23:32 -08:00
Alan Cohen
5b2c4aef2d Set User-Agent Header with ripple-lib/{version}
- This would be helpful for us to analyze usage of ripple-lib for anyone
  using the public rippled
2015-02-20 09:06:02 -08:00
Chris Clark
b7ccf424f4 [TASK] Update bignumber.js and use new feature to simplify our code 2015-02-19 19:51:05 -08:00
Geert Weening
77d5db168b Merge pull request #283 from clark800/feature/amount_sanity
[TASK] Disable parsing native amounts in foating point format
2015-02-17 14:29:38 -08:00
Chris Clark
e80cd1ff55 [TASK] Disable parsing native amounts in foating point format 2015-02-17 14:03:23 -08:00
Geert Weening
4ff25a21f6 Merge pull request #282 from boxbag/orderbook
[TEST] test and refactor orderbooks
2015-02-17 13:35:55 -08:00
Bo Chen
f184a71360 [TEST] test and refactor orderbooks 2015-02-13 13:37:57 -08:00
wltsmrz
fc38a9853d Merge pull request #281 from shekenahglory/develop
fix handling of false parameters in requestLedger
2015-02-11 14:24:27 -08:00
Matthew Fettig
6023efed41 fix handling of false parameters in requestLedger 2015-02-11 14:11:24 -08:00
465 changed files with 25659 additions and 51404 deletions

View File

@@ -1,7 +1,14 @@
[ignore]
.*/src/api/.*
.*/src/core/.*
.*/dist/.*
.*/test/fixtures/.*
.*/node_modules/flow-bin/.*
[include]
./node_modules/
[libs]
[options]
module.system=node

4
.gitignore vendored
View File

@@ -17,7 +17,7 @@
# Ignore object files.
*.o
build/*.js
build/
tags
bin/rippled
Debug/*.*
@@ -59,3 +59,5 @@ out/
# Ignore perf test cache
scripts/cache
eslintrc

View File

@@ -1,3 +1,4 @@
deploy
lib-cov
coverage.html
src
dist/bower

View File

@@ -1,14 +1,9 @@
sudo: false # use faster docker containers
language: node_js
node_js:
- "0.10"
script: MOCHA_REPORTER=tap npm test --coverage
after_success:
- npm run coveralls
- "0.12"
before_script:
- sh -c "git log | head -12"
script: bin/ci.sh
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/d1ec4245f90231619d30
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false

View File

@@ -1,102 +1,51 @@
/* eslint-disable no-var, no-param-reassign */
/* these eslint rules are disabled because gulp does not support babel yet */
'use strict';
var _ = require('lodash');
var gulp = require('gulp');
var gutil = require('gulp-util');
var watch = require('gulp-watch');
var plumber = require('gulp-plumber');
var filelog = require('gulp-filelog');
var cleanDest = require('gulp-clean-dest');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var webpack = require('webpack');
var map = require('map-stream');
var bump = require('gulp-bump');
var react = require('gulp-react');
var flow = require('gulp-flowtype');
var argv = require('yargs').argv;
//var header = require('gulp-header');
var pkg = require('./package.json');
var banner = '/*! <%= pkg.name %> - v<%= pkg.version %> - '
+ '<%= new Date().toISOString() %>\n'
+ '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>'
+ '* Copyright (c) <%= new Date().getFullYear() %> <%= pkg.author.name %>;'
+ ' Licensed <%= pkg.license %> */'
var sjclSrc = [
'src/js/sjcl/core/sjcl.js',
'src/js/sjcl/core/aes.js',
'src/js/sjcl/core/bitArray.js',
'src/js/sjcl/core/codecString.js',
'src/js/sjcl/core/codecHex.js',
'src/js/sjcl/core/codecBase64.js',
'src/js/sjcl/core/codecBytes.js',
'src/js/sjcl/core/sha256.js',
'src/js/sjcl/core/sha512.js',
'src/js/sjcl/core/sha1.js',
'src/js/sjcl/core/ccm.js',
// 'src/js/sjcl/core/cbc.js',
// 'src/js/sjcl/core/ocb2.js',
'src/js/sjcl/core/hmac.js',
'src/js/sjcl/core/pbkdf2.js',
'src/js/sjcl/core/random.js',
'src/js/sjcl/core/convenience.js',
'src/js/sjcl/core/bn.js',
'src/js/sjcl/core/ecc.js',
'src/js/sjcl/core/srp.js',
'src/js/sjcl-custom/sjcl-ecc-pointextras.js',
'src/js/sjcl-custom/sjcl-secp256k1.js',
'src/js/sjcl-custom/sjcl-ripemd160.js',
'src/js/sjcl-custom/sjcl-extramath.js',
'src/js/sjcl-custom/sjcl-montgomery.js',
'src/js/sjcl-custom/sjcl-validecc.js',
'src/js/sjcl-custom/sjcl-ecdsa-canonical.js',
'src/js/sjcl-custom/sjcl-ecdsa-der.js',
'src/js/sjcl-custom/sjcl-ecdsa-recoverablepublickey.js',
'src/js/sjcl-custom/sjcl-jacobi.js'
];
function logPluginError(error) {
gutil.log(error.toString());
function webpackConfig(extension, overrides) {
overrides = overrides || {};
var defaults = {
cache: true,
entry: './src/index.js',
output: {
library: 'ripple',
path: './build/',
filename: ['ripple-', extension].join(pkg.version)
},
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader?optional=runtime'
}]
}
};
return _.assign({}, defaults, overrides);
}
gulp.task('concat-sjcl', function() {
return gulp.src(sjclSrc)
.pipe(concat('sjcl.js'))
.pipe(gulp.dest('./build/'));
gulp.task('build', function(callback) {
webpack(webpackConfig('.js'), callback);
});
gulp.task('build', [ 'concat-sjcl' ], function(callback) {
webpack({
cache: true,
entry: './src/js/ripple/index.js',
output: {
library: 'ripple',
path: './build/',
filename: [ 'ripple-', '.js' ].join(pkg.version)
},
}, callback);
});
gulp.task('build-min', [ 'build' ], function(callback) {
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
gulp.task('build-min', ['build'], function() {
return gulp.src(['./build/ripple-', '.js'].join(pkg.version))
.pipe(uglify())
.pipe(rename([ 'ripple-', '-min.js' ].join(pkg.version)))
.pipe(rename(['ripple-', '-min.js'].join(pkg.version)))
.pipe(gulp.dest('./build/'));
});
gulp.task('build-debug', [ 'concat-sjcl' ], function(callback) {
webpack({
cache: true,
entry: './src/js/ripple/index.js',
output: {
library: 'ripple',
path: './build/',
filename: [ 'ripple-', '-debug.js' ].join(pkg.version)
},
debug: true,
devtool: 'eval'
}, callback);
gulp.task('build-debug', function(callback) {
var configOverrides = {debug: true, devtool: 'eval'};
webpack(webpackConfig('-debug.js', configOverrides), callback);
});
/**
@@ -105,97 +54,73 @@ gulp.task('build-debug', [ 'concat-sjcl' ], function(callback) {
*/
function buildUseError(cons) {
return 'var {<CONS>:function(){throw new Error("Class is unavailable in this build: <CONS>")}}'
.replace(new RegExp('<CONS>', 'g'), cons);
};
return ('var {<CONS>:function(){throw new Error('
+ '"Class is unavailable in this build: <CONS>")}}')
.replace(new RegExp('<CONS>', 'g'), cons);
}
gulp.task('build-core', [ 'concat-sjcl' ], function(callback) {
webpack({
entry: [
'./src/js/ripple/remote.js'
],
externals: [
{
'./transaction': buildUseError('Transaction'),
'./orderbook': buildUseError('OrderBook'),
'./account': buildUseError('Account'),
'./serializedobject': buildUseError('SerializedObject')
}
],
output: {
library: 'ripple',
path: './build/',
filename: [ 'ripple-', '-core.js' ].join(pkg.version)
},
gulp.task('build-core', function(callback) {
var configOverrides = {
cache: false,
entry: './src/remote.js',
externals: [{
'./transaction': buildUseError('Transaction'),
'./orderbook': buildUseError('OrderBook'),
'./account': buildUseError('Account'),
'./serializedobject': buildUseError('SerializedObject')
}],
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}, callback);
};
webpack(webpackConfig('-core.js', configOverrides), callback);
});
gulp.task('bower-build', [ 'build' ], function(callback) {
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
gulp.task('bower-build', ['build'], function() {
return gulp.src(['./build/ripple-', '.js'].join(pkg.version))
.pipe(rename('ripple.js'))
.pipe(gulp.dest('./dist/'));
.pipe(gulp.dest('./dist/bower'));
});
gulp.task('bower-build-min', [ 'build-min' ], function(callback) {
return gulp.src([ './build/ripple-', '-min.js' ].join(pkg.version))
gulp.task('bower-build-min', ['build-min'], function() {
return gulp.src(['./build/ripple-', '-min.js'].join(pkg.version))
.pipe(rename('ripple-min.js'))
.pipe(gulp.dest('./dist/'));
.pipe(gulp.dest('./dist/bower'));
});
gulp.task('bower-build-debug', [ 'build-debug' ], function(callback) {
return gulp.src([ './build/ripple-', '-debug.js' ].join(pkg.version))
gulp.task('bower-build-debug', ['build-debug'], function() {
return gulp.src(['./build/ripple-', '-debug.js'].join(pkg.version))
.pipe(rename('ripple-debug.js'))
.pipe(gulp.dest('./dist/'));
.pipe(gulp.dest('./dist/bower'));
});
gulp.task('bower-version', function() {
gulp.src('./dist/bower.json')
.pipe(bump({ version: pkg.version }))
.pipe(gulp.dest('./dist/'));
gulp.src('./dist/bower/bower.json')
.pipe(bump({version: pkg.version}))
.pipe(gulp.dest('./dist/bower'));
});
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug', 'bower-version']);
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug',
'bower-version']);
gulp.task('watch', function() {
gulp.watch('src/js/ripple/*', [ 'build-debug' ]);
});
// To use this, each javascript file must have /* @flow */ on the first line
gulp.task('typecheck', function() {
return gulp.src('src/js/ripple/*.js')
.pipe(flow({ // note: do not set the 'all' option, it is broken
weak: true, // remove this after all errors are addressed
killFlow: true
}));
});
gulp.task('strip', function() {
return gulp.src('src/js/ripple/*.js')
.pipe(watch('src/js/ripple/*.js'))
.pipe(cleanDest('out')) // delete outdated output file before stripping
.pipe(plumber()) // prevent an error in one file from ending build
.pipe(react({ stripTypes: true }).on('error', logPluginError))
.pipe(filelog())
.pipe(gulp.dest('out'));
gulp.watch('src/*', ['build-debug']);
});
gulp.task('version-bump', function() {
if (!argv.type) {
throw new Error("No type found, pass it in using the --type argument");
throw new Error('No type found, pass it in using the --type argument');
}
gulp.src('./package.json')
.pipe(bump({ type: argv.type }))
.pipe(bump({type: argv.type}))
.pipe(gulp.dest('./'));
});
gulp.task('version-beta', function() {
gulp.src('./package.json')
.pipe(bump({ version: pkg.version + '-beta' }))
.pipe(bump({version: pkg.version + '-beta'}))
.pipe(gulp.dest('./'));
});
gulp.task('default', [ 'concat-sjcl', 'build', 'build-debug', 'build-min' ]);
gulp.task('default', ['build', 'build-debug', 'build-min']);

View File

@@ -1,3 +1,91 @@
##0.12.15
+ [Add offer autobridging](https://github.com/ripple/ripple-lib/commit/c7bbce83719c1e8c6a4fae5ca850e7515db1a4a5)
+ [Prevent crash when listening for "model" events on the OrderBook class](https://github.com/ripple/ripple-lib/commit/5824c3cb7cb6bd834d6e037f69943aebf3d83351)
+ [Fix empty order edgecase](https://github.com/ripple/ripple-lib/commit/64809d9ae23dc24f47accd4b4788b48f49880d3e)
+ [Fix AutobridgeCalculator (RT-3445)](https://github.com/ripple/ripple-lib/commit/1fff5ea6dcbcee856536df26f3b9cf1aec3c3b55)
+ [Update sjcl and delete custom ripemd160, montgomery, and jacobi](https://github.com/ripple/ripple-lib/commit/50cda426eb83599c38c0b725e1524a01fc415da2)
+ [Fix transaction summary for transactions that fail with remoteError](https://github.com/ripple/ripple-lib/commit/5e714f6143464d7912f42537acaa553b88eaf6dc)
+ [Fix serializedobject append for excessively large bytes length](https://github.com/ripple/ripple-lib/commit/e93f1ab6f4aaad347450aee75a169af0faa2121c)
+ [Switch to sjcl npm module](https://github.com/ripple/ripple-lib/commit/9a502580fd89ec6a9aa55f4e5847f6a4a2cb5bba)
+ [Add babel transpiler](https://github.com/ripple/ripple-lib/commit/398f8d001f758bf575b959537a17e79e4042d17b)
+ [Remove unused float.js and wallet.js](https://github.com/ripple/ripple-lib/commit/d4a4b5f4fbbf09677a59ce81bace35c6426a2fda)
+ [Remove config singleton to reduce global state](https://github.com/ripple/ripple-lib/commit/c655c2a20ee5d150a4b5a1b6717b9fb81f636025)
##0.12.4
+ [Improve entropy security](https://github.com/ripple/ripple-lib/commit/c7ba822320880037796f57876d1abb4e525648ed)
+ [Remove unused crypt.js file](https://github.com/ripple/ripple-lib/commit/1f68eba1461bca03a4d22872450d15ae5a185334)
##0.12.3
+ [Add getLedgerSequence to Remote](https://github.com/ripple/ripple-lib/commit/d09548d04d3238fca653d482ec1d5faa7254559a)
+ [Improve randomness when generating ECDSA signatures](https://github.com/ripple/ripple-lib/commit/fe7e30b737ead6e71adfa466f5835ba546feab31)
+ [Improve SerializedObject.append performance](https://github.com/ripple/ripple-lib/commit/f7c35b118ebba549a64bcaa1a0629385ec6dbf6f)
+ [Add `Amount.scale`. Multiply an amounts value by a scale factor](https://github.com/ripple/ripple-lib/commit/74dac97b368493056474468520f05671f458a69f)
##0.12.2
+ [Check that stack trace is available, fixes logging in browser](https://github.com/ripple/ripple-lib/commit/53cae3a66d48e88e8a6bbb96d6489ce7b9e22975)
##0.12.1
**Breaking Changes**
+ [Removed support for parsing native amounts in floating point format](https://github.com/ripple/ripple-lib/commit/e80cd1ff55deae9cd5b0ae85be957f86856b887e)
**Changes**
+ [Fix taker pays funded calculation](https://github.com/ripple/ripple-lib/commit/5af824f5cf46c7b9caa58ee0a757bf854d26c8dc)
+ [Fix order funded amount calculation](https://github.com/ripple/ripple-lib/commit/b2cdb1a6aed968b1f306e8dadbd4b7ca37e5aa03)
+ [Fix handling of quality in order book](https://github.com/ripple/ripple-lib/commit/2a5a8b498da60df738ba18d5c265f34771e8a1af)
+ [Fix currency parsing of non-alphanumeric and no-currency currencies](https://github.com/ripple/ripple-lib/commit/2166bb2e88eae8d5f1aba77338f69e8a9edf6a6f)
+ [Add Amount.strict_mode for toggling range validation](https://github.com/ripple/ripple-lib/commit/b5ed8f59a7dab1a17491618b8d9193646c314fb4)
+ [Add filename and line number to log, use log.warn() for deprecations](https://github.com/ripple/ripple-lib/commit/90329d3d73f1a76675063655b407513e32dc048b)
+ [Add GlobalFreeze and NoFreeze flags](https://github.com/ripple/ripple-lib/commit/e2ed2bdbf6f01c7d4d690c2cf0b83fba94558dd7)
+ [Fix handling of falsy parameters in requestLedger](https://github.com/ripple/ripple-lib/commit/6023efed41b7812b3bab660a1c0dc9f0a21000b9)
+ [Fix Base:decode](https://github.com/ripple/ripple-lib/commit/719f39c01c6941d9a650aa94f95617793dd53ea0)
+ [Fix Amount: clone in ratio_human, product_human](https://github.com/ripple/ripple-lib/commit/19e17a8431550cf156b1ad669a19dedfe4e28e4a)
+ [Fix Amount.to_human for very small numbers](https://github.com/ripple/ripple-lib/commit/6abfa759aa09d68074ac558d96c4b126a7cd1719)
+ [Refactor base conversion](https://github.com/ripple/ripple-lib/commit/f2b63fa4a80663eb29472bc6bb1aea8159f1f205)
+ [Update binary transaction format](https://github.com/ripple/ripple-lib/commit/8e134918fb4c22983320a3102f955e4568bb1dfb)
+ [Add DefaultRipple account flag](https://github.com/ripple/ripple-lib/commit/3e249902c4cf25b4da5e75048c84ae391be83b10)
+ [Remove `Features` field requirement in `SetFee` transaction format](https://github.com/ripple/ripple-lib/commit/a20a649013646710c078d4ce1e210f87c7fe74fe)
+ [Remove `RegularKey` field requirement in `SetRegularKey` transaction format](https://github.com/ripple/ripple-lib/commit/c275174f27877ba8f389eb4efe969feb514d6e46)
##0.12.0
**Breaking Changes**
@@ -14,8 +102,8 @@
- [Handle invalid input in parse_human - c8f18c8c](https://github.com/ripple/ripple-lib/commit/c8f18c8c8590b7b48e370e0325b6677b7720294f)
- [Check for null in isNumber - b86790c8](https://github.com/ripple/ripple-lib/commit/b86790c8543c239a532fd7697d4652829019d385)
- [Cleanup amount.js - d0fb291c](https://github.com/ripple/ripple-lib/commit/d0fb291c4e330193a244902156f1d74730da357d)
**Changes**
+ [Add deprecation warnings to request constructors. The first argument to request constructor functions should be an object containing request properties](https://github.com/ripple/ripple-lib/commit/35d76b3520934285f80059c1badd6c522539104c)
@@ -30,13 +118,13 @@
+ [Bumped dependencies](https://github.com/ripple/ripple-lib/commit/f9bc7cc746b44b24b61bbe260ae2e9d9617286da)
##0.11.0
+ [Track the funded status of an order based on cumulative account orders](https://github.com/ripple/ripple-lib/commit/67d39737a4d5e0fcd9d9b47b9083ee00e5a9e652) and [67d3973](https://github.com/ripple/ripple-lib/commit/b6b99dde022e1e14c4797e454b1d7fca50e49482)
+ Remove blobvault client from ripple-lib, use the [`ripple-vault-client`](https://github.com/ripple/ripple-vault-client) instead [9b3d62b7](https://github.com/ripple/ripple-lib/commit/9b3d62b765c4c25beae6eb0fa57ef3a07f2581b1)
+ Remove blobvault client from ripple-lib, use the [`ripple-vault-client`](https://github.com/ripple/ripple-vault-client) instead [9b3d62b7](https://github.com/ripple/ripple-lib/commit/9b3d62b765c4c25beae6eb0fa57ef3a07f2581b1)
+ [Add support for `ledger` option in requestBookOffers](https://github.com/ripple/ripple-lib/commit/34c0677c453c409ef0a5b351959abdc176d3bacb)
@@ -75,13 +163,13 @@ are locally determined to have expired: `tejMaxLedger`.
+ [Improve memo support](https://github.com/ripple/ripple-lib/commit/1704ac4ae144c0ce54afad86f644c75a632080b1)
- Add `MemoFormat` property for memo
- Enforce `MemoFormat` and `MemoType` to be valid ASCII
- Support `text` and `json` MemoFormat
- Support `text` and `json` MemoFormat
+ [Update jscl library](https://github.com/ripple/ripple-lib/commit/3204998fcb6f31d6c90532a737a4adb8a1e420f6)
- Improved entropy by taking advantage of platform crypto
- Use jscl's k256 curve instead of altering the c256 curve with k256 configuration
- **deprecated:** the c256 curve is linked to the k256 curve to provide backwards compatibility, this link will be removed in the future
+ [Fix empty queue check on reconnect](https://github.com/ripple/ripple-lib/commit/3c21994adcf72d1fbd87d453ceb917f9ad6df4ec)
##0.9.4
@@ -110,7 +198,7 @@ are locally determined to have expired: `tejMaxLedger`.
+ [**Breaking change**: Change accountRequest method signature](https://github.com/ripple/ripple-lib/commit/6f5d1104aa3eb440c518ec4f39e264fdce15fa15)
+ [Add paging behavior for account requests, `account_lines` and `account_offers`](https://github.com/ripple/ripple-lib/commit/722f4e175dbbf378e51b49142d0285f87acb22d7)
+ [Add paging behavior for account requests, `account_lines` and `account_offers`](https://github.com/ripple/ripple-lib/commit/722f4e175dbbf378e51b49142d0285f87acb22d7)
+ [Add max_fee setter to transactions to set max fee the submitter is willing to pay] (https://github.com/ripple/ripple-lib/commit/24587fab9c8ad3840d7aa345a7037b48839e09d7)
@@ -126,7 +214,7 @@ var options = {
ledger: < valid ledger_index or ledger_hash >
}
// The `marker` comes back in an account request if there are more results than are returned
// The `marker` comes back in an account request if there are more results than are returned
// in the current response. The amount of results per response are determined by the `limit`.
if (marker) {
options.marker = < marker >;

View File

@@ -44,7 +44,7 @@ A JavaScript API for interacting with Ripple in Node.js and the browser
$ bower install ripple
```
See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions
See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions.
**Building ripple-lib for browser environments**

52
bin/ci.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/bin/bash -ex
NODE_INDEX="$1"
TOTAL_NODES="$2"
typecheck() {
npm install -g flow-bin
npm run typecheck
}
lint() {
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$")
}
unittest() {
npm test --coverage
npm run coveralls
}
oneNode() {
lint
typecheck
unittest
}
twoNodes() {
case "$NODE_INDEX" in
0) lint && unittest;;
1) typecheck;;
*) echo "ERROR: invalid usage"; exit 2;;
esac
}
threeNodes() {
case "$NODE_INDEX" in
0) lint;;
1) typecheck;;
2) unittest;;
*) echo "ERROR: invalid usage"; exit 2;;
esac
}
case "$TOTAL_NODES" in
"") oneNode;;
1) oneNode;;
2) twoNodes;;
3) threeNodes;;
*) echo "ERROR: invalid usage"; exit 2;;
esac

View File

@@ -1,47 +1,52 @@
#!/usr/bin/env node
/* eslint-disable no-var */
'use strict';
var SerializedObject = require('..').SerializedObject;
var SerializedObject = require('../src/js/ripple/serializedobject').SerializedObject;
function main() {
var argv = process.argv.slice(2);
var blob = argv.shift();
var argv = process.argv.slice(2);
var blob;
blob = argv.shift();
if (blob === '-') {
read_input(ready);
} else {
ready();
if (blob === '-') {
read_input(ready);
} else {
ready(blob);
}
}
function read_input(callback) {
tx_json = '';
process.stdin.on('data', function(data) { tx_json += data; });
var tx_json = '';
process.stdin.on('data', function(data) {
tx_json += data;
});
process.stdin.on('end', callback);
process.stdin.resume();
}
function ready() {
function ready(blob) {
var valid_arguments = blob;
if (!valid_arguments) {
console.error('Invalid arguments\n');
print_usage();
} else {
decode();
}
decode(blob);
}
}
function print_usage() {
/* eslint-disable max-len */
console.log(
'Usage: decode_binary.js <hex_blob>\n\n',
'Example: decode_binary.js 120000240000000161D6871AFD498D00000000000000000000000000005553440000000000550FC62003E785DC231A1058A05E56E3F09CF4E668400000000000000A732102AE75B908F0A95F740A7BFA96057637E5C2170BC8DAD13B2F7B52AE75FAEBEFCF811450F97A072F1C4357F1AD84566A609479D927C9428314550FC62003E785DC231A1058A05E56E3F09CF4E6'
);
};
/* eslint-enable max-len */
}
function decode() {
buffer = new SerializedObject(blob);
function decode(blob) {
var buffer = new SerializedObject(blob);
console.log(buffer.to_json());
};
}
main();
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,62 +1,23 @@
#!/usr/bin/env node
var Transaction = require('../src/js/ripple/transaction').Transaction;
var argv = process.argv.slice(2);
var verbose;
var secret;
var tx_json;
if (~argv.indexOf('-v')){
argv.splice(argv.indexOf('-v'), 1);
verbose = true;
}
secret = argv.shift();
tx_json = argv.shift();
if (tx_json === '-') {
read_input(ready);
} else {
ready();
}
/* eslint-disable no-var */
'use strict';
var Transaction = require('..').Transaction;
function read_input(callback) {
tx_json = '';
process.stdin.on('data', function(data) { tx_json += data; });
process.stdin.on('end', callback);
var stdin = '';
process.stdin.on('data', function(data) {
stdin += data;
});
process.stdin.on('end', function() {
callback(stdin);
});
process.stdin.resume();
}
function ready() {
var valid_arguments = secret && tx_json;
if (!valid_arguments) {
console.error('Invalid arguments\n');
print_usage();
} else {
var valid_json = true;
try {
tx_json = JSON.parse(tx_json);
} catch(exception) {
valid_json = false;
}
if (!valid_json) {
console.error('Invalid JSON\n');
print_usage();
} else {
sign_transaction();
}
}
}
function print_usage() {
console.log(
'Usage: rsign.js <secret> <json>\n\n',
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC','\''+
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC', '\'' +
JSON.stringify({
TransactionType: 'Payment',
Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
@@ -64,14 +25,14 @@ function print_usage() {
Amount: '200000000',
Fee: '10',
Sequence: 1
})+'\''
);
};
}) + '\''
);
}
function sign_transaction() {
function sign_transaction(tx_json_object, secret, verbose) {
var tx = new Transaction();
tx.tx_json = tx_json;
tx.tx_json = tx_json_object;
tx._secret = secret;
tx.complete();
@@ -81,16 +42,56 @@ function sign_transaction() {
if (verbose) {
var sim = { };
sim.tx_blob = tx.serialize().to_hex();
sim.tx_json = tx.tx_json;
sim.tx_blob = tx.serialize().to_hex();
sim.tx_json = tx.tx_json;
sim.tx_signing_hash = unsigned_hash;
sim.tx_unsigned = unsigned_blob;
sim.tx_unsigned = unsigned_blob;
console.log(JSON.stringify(sim, null, 2));
} else {
console.log(tx.serialize().to_hex());
}
};
}
function ready(tx_json, secret, verbose) {
if (!(tx_json && secret)) {
console.error('Invalid arguments\n');
print_usage();
return;
}
var tx_json_object;
try {
tx_json_object = JSON.parse(tx_json);
} catch(exception) {
console.error('Invalid JSON\n');
print_usage();
return;
}
sign_transaction(tx_json_object, secret, verbose);
}
function main() {
var argv = process.argv.slice(2);
var verbose;
var secret;
var tx_json;
if (~argv.indexOf('-v')) {
argv.splice(argv.indexOf('-v'), 1);
verbose = true;
}
secret = argv.shift();
tx_json = argv.shift();
if (tx_json === '-') {
read_input(function(stdin) {
ready(stdin, secret, verbose);
});
} else {
ready(tx_json, secret, verbose);
}
}
main();
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,12 +1,16 @@
#!/usr/bin/env node
/* eslint-disable no-var */
'use strict';
var UInt160 = require('..').UInt160;
var UInt160 = require('../').UInt160;
var address = process.argv[2];
function main() {
var address = process.argv[2];
if (address === '-') {
readInput(validateAddress);
} else {
validateAddress(address);
if (address === '-') {
readInput(validateAddress);
} else {
validateAddress(address);
}
}
function readInput(callback) {
@@ -19,8 +23,10 @@ function readInput(callback) {
process.stdin.on('end', function() {
callback(result);
});
};
}
function validateAddress(address) {
process.stdout.write((UInt160.is_valid(address.trim()) ? '0' : '1') + '\r\n');
};
}
main();

7
circle.yml Normal file
View File

@@ -0,0 +1,7 @@
machine:
node:
version: 0.12.0
test:
override:
- bin/ci.sh "$CIRCLE_NODE_INDEX" "$CIRCLE_NODE_TOTAL":
parallel: true

View File

@@ -1,3 +0,0 @@
start newcoin
sleep 4
start index.html

View File

@@ -18,7 +18,7 @@ This file provides step-by-step walkthroughs for some of the most common usages
##Connecting to the Ripple network
1. [Get ripple-lib](README.md#getting-ripple-lib)
1. [Get ripple-lib](../README.md#installation)
2. Load the ripple-lib module into a Node.js file or webpage:
```js
/* Loading ripple-lib with Node.js */
@@ -37,7 +37,7 @@ This file provides step-by-step walkthroughs for some of the most common usages
servers: [
{ host: 's-west.ripple.com', port: 443, secure: true }
]
}
};
var remote = new Remote(options);
@@ -173,7 +173,7 @@ See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on s
##Submitting a payment to the network
Submitting a payment transaction to the Ripple network involves connecting to a `Remote`, creating a transaction, signing it with the user's secret, and submitting it to the `rippled` server. Note that the `Amount` module is used to convert human-readable amounts like '1XRP' or '10.50USD' to the type of Amount object used by the Ripple network.
Submitting a payment transaction to the Ripple network involves connecting to a `Remote`, creating a transaction, signing it with the user's secret, and submitting it to the `rippled` server. Note that the `Amount` module is used to convert human-readable amounts like '1 XRP' or '10.50 USD' to the type of Amount object used by the Ripple network.
```js
/* Loading ripple-lib Remote and Amount modules in Node.js */
@@ -187,7 +187,7 @@ var Amount = require('ripple-lib').Amount;
var MY_ADDRESS = 'rrrMyAddress';
var MY_SECRET = 'secret';
var RECIPIENT = 'rrrRecipient';
var AMOUNT = Amount.from_human('1XRP');
var AMOUNT = Amount.from_human('1 USD').set_issuer('rrrIssuer');
var remote = new Remote({ /* Remote options */ });

View File

@@ -1,72 +0,0 @@
{
"env": {
"browser": true,
"node": true
},
"rules": {
"no-use-before-define": 1,
"no-undef": 1,
"no-unused-expressions": 1,
"no-unused-vars": 1,
"no-extend-native": 1,
"no-native-reassign": 1,
"no-trailing-spaces": 1,
"no-empty": 1,
"no-inner-declarations": 1,
"no-irregular-whitespace": 1,
"no-negated-in-lhs": 1,
"no-obj-calls": 1,
"no-reserved-keys": 1,
"no-sparse-arrays": 1,
"no-unreachable": 1,
"use-isnan": 1,
"valid-jsdoc": 1,
"valid-typeof": 1,
"block-scoped-var": 1,
"dot-notation": 1,
"semi": 1,
"curly": 1,
"eqeqeq": 1,
"no-else-return": 1,
"new-cap": 1,
"new-parens": 1,
"no-comma-dangle": 1,
"no-empty-label": 1,
"no-eval": 1,
"no-extra-bind": 1,
"no-fallthrough": 1,
"no-lone-blocks": 1,
"no-loop-func": 1,
"no-multi-spaces": 1,
"no-return-assign": 1,
"no-sequences": 1,
"no-with": 1,
"radix": 1,
"yoda": [ 1, "never" ],
"no-catch-shadow": 1,
"no-shadow-restricted-names": 1,
"no-delete-var": 1,
"no-undefined": 1,
"handle-callback-err": 1,
"brace-style": [ 1, "1tbs", { "allowSingleLine": false } ],
"comma-spacing": [ 1, { "before": false, "after": true } ],
"comma-style": [ 1, "last" ],
"consistent-this": [ 1, "self" ],
"func-style": [ 1, "declaration" ],
"key-spacing": [ 1, { "beforeColon": false, "afterColon": true } ],
"max-nested-callbacks": [ 1, 2 ],
"no-lonely-if": 1,
"no-mixed-spaces-and-tabs": 1,
"no-multiple-empty-lines": 1,
"no-space-before-semi": 1,
"no-spaced-func": 1,
"space-after-keywords": [ 1, "always" ],
"space-infix-ops": 1,
"space-return-throw-case": 1,
"spaced-line-comment": 1,
"max-params": [ 1, 4 ],
"max-depth": [1, 3 ],
"max-len": [ 1, 80 ],
"quotes": [ 1, "single" ]
}
}

249
npm-shrinkwrap.json generated
View File

@@ -1,181 +1,162 @@
{
"name": "ripple-lib",
"version": "0.12.0",
"version": "0.13.0-rc1",
"npm-shrinkwrap-version": "5.4.0",
"node-version": "v0.12.6",
"dependencies": {
"async": {
"version": "0.9.0",
"from": "async@>=0.9.0 <0.10.0",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.0.tgz"
"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",
"dependencies": {
"core-js": {
"version": "0.9.18",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-0.9.18.tgz"
}
}
},
"bignumber.js": {
"version": "2.0.0",
"from": "bignumber.js@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.0.tgz"
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz"
},
"extend": {
"version": "1.2.1",
"from": "extend@>=1.2.1 <1.3.0",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
},
"lodash": {
"version": "3.1.0",
"from": "lodash@>=3.1.0 <4.0.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.1.0.tgz"
},
"lru-cache": {
"version": "2.5.0",
"from": "lru-cache@>=2.5.0 <2.6.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.0.tgz"
},
"ripple-wallet-generator": {
"version": "1.0.1",
"from": "ripple-wallet-generator@1.0.1",
"resolved": "https://registry.npmjs.org/ripple-wallet-generator/-/ripple-wallet-generator-1.0.1.tgz"
},
"superagent": {
"version": "0.18.2",
"from": "superagent@>=0.18.0 <0.19.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-0.18.2.tgz",
"https-proxy-agent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz",
"dependencies": {
"qs": {
"version": "0.6.6",
"from": "qs@0.6.6",
"resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz"
},
"formidable": {
"version": "1.0.14",
"from": "formidable@1.0.14",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
},
"mime": {
"version": "1.2.11",
"from": "mime@1.2.11",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
},
"component-emitter": {
"version": "1.1.2",
"from": "component-emitter@1.1.2",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz"
},
"methods": {
"version": "1.0.1",
"from": "methods@1.0.1",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.0.1.tgz"
},
"cookiejar": {
"version": "2.0.1",
"from": "cookiejar@2.0.1",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.0.1.tgz"
"agent-base": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.0.0.tgz",
"dependencies": {
"semver": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
}
}
},
"debug": {
"version": "1.0.4",
"from": "debug@>=1.0.1 <1.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-1.0.4.tgz",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"dependencies": {
"ms": {
"version": "0.6.2",
"from": "ms@0.6.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
}
}
},
"reduce-component": {
"version": "1.0.1",
"from": "reduce-component@1.0.1",
"resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz"
"extend": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
}
}
},
"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",
"dependencies": {
"generate-function": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
},
"form-data": {
"version": "0.1.3",
"from": "form-data@0.1.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.3.tgz",
"generate-object-property": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
"dependencies": {
"combined-stream": {
"version": "0.0.7",
"from": "combined-stream@>=0.0.4 <0.1.0",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz",
"dependencies": {
"delayed-stream": {
"version": "0.0.5",
"from": "delayed-stream@0.0.5",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz"
}
}
"is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
}
}
},
"readable-stream": {
"version": "1.0.27-1",
"from": "readable-stream@1.0.27-1",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.27-1.tgz",
"dependencies": {
"core-util-is": {
"version": "1.0.1",
"from": "core-util-is@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz"
},
"isarray": {
"version": "0.0.1",
"from": "isarray@0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
},
"string_decoder": {
"version": "0.10.31",
"from": "string_decoder@>=0.10.0 <0.11.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
},
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
"jsonpointer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-1.1.0.tgz"
},
"xtend": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
}
}
},
"lodash": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.0.tgz"
},
"lru-cache": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz"
},
"ripple-lib-transactionparser": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.3.2.tgz",
"dependencies": {
"bignumber.js": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-1.4.1.tgz"
}
}
},
"ripple-wallet-generator": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/ripple-wallet-generator/-/ripple-wallet-generator-1.0.3.tgz"
},
"simple-asyncify": {
"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"
}
}
},
"ws": {
"version": "0.7.1",
"from": "ws@>=0.7.1 <0.8.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.1.tgz",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz",
"dependencies": {
"options": {
"version": "0.0.6",
"from": "options@>=0.0.5",
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz"
},
"ultron": {
"version": "1.0.1",
"from": "ultron@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.1.tgz"
},
"bufferutil": {
"version": "1.0.1",
"from": "bufferutil@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.0.1.tgz",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.1.0.tgz",
"dependencies": {
"bindings": {
"version": "1.2.1",
"from": "bindings@>=1.2.0 <1.3.0"
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
},
"nan": {
"version": "1.6.1",
"from": "nan@>=1.6.0 <1.7.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.6.1.tgz"
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz"
}
}
},
"options": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz"
},
"ultron": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz"
},
"utf-8-validate": {
"version": "1.0.1",
"from": "utf-8-validate@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.0.1.tgz",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.1.0.tgz",
"dependencies": {
"bindings": {
"version": "1.2.1",
"from": "bindings@>=1.2.0 <1.3.0"
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
},
"nan": {
"version": "1.6.1",
"from": "nan@>=1.6.0 <1.7.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.6.1.tgz"
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz"
}
}
}

View File

@@ -1,58 +1,64 @@
{
"name": "ripple-lib",
"version": "0.12.0",
"version": "0.13.0-rc1",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
"src/js/*",
"dist/npm/*",
"bin/*",
"build/*",
"test/*",
"Makefile",
"Gulpfile.js"
],
"main": "src/js/ripple",
"main": "dist/npm/",
"directories": {
"test": "test"
},
"dependencies": {
"async": "~0.9.0",
"bignumber.js": "^2.0.0",
"babel-runtime": "^5.5.4",
"bignumber.js": "^2.0.3",
"extend": "~1.2.1",
"https-proxy-agent": "^1.0.0",
"is-my-json-valid": "^2.12.0",
"lodash": "^3.1.0",
"lru-cache": "~2.5.0",
"ripple-wallet-generator": "1.0.1",
"ws": "~0.7.1",
"superagent": "^0.18.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",
"ws": "~0.7.1"
},
"devDependencies": {
"assert-diff": "0.0.4",
"assert-diff": "^1.0.1",
"babel": "^5.5.4",
"babel-core": "^5.5.4",
"babel-eslint": "^3.1.23",
"babel-loader": "^5.0.0",
"coveralls": "~2.10.0",
"eslint": "^0.13.0",
"eslint": "^0.24.0",
"eslint-plugin-flowtype": "^1.0.0",
"eventemitter2": "^0.4.14",
"flow-bin": "^0.13.1",
"gulp": "~3.8.10",
"gulp-bump": "~0.1.13",
"gulp-clean-dest": "^0.1.0",
"gulp-concat": "~2.4.3",
"gulp-filelog": "^0.4.1",
"gulp-flowtype": "^0.4.1",
"gulp-plumber": "^0.6.6",
"gulp-react": "^2.0.0",
"gulp-rename": "~1.2.0",
"gulp-uglify": "~1.1.0",
"gulp-util": "^3.0.3",
"gulp-watch": "^4.1.0",
"istanbul": "~0.3.5",
"map-stream": "~0.1.0",
"mocha": "~2.1.0",
"nock": "^0.34.1",
"webpack": "~1.5.3",
"yargs": "~1.3.1"
},
"scripts": {
"build": "node_modules/.bin/gulp",
"pretest": "node_modules/.bin/gulp concat-sjcl",
"test": "./node_modules/.bin/istanbul test -x build/sjcl.js -x src/js/jsbn/* ./node_modules/mocha/bin/_mocha -- --reporter ${MOCHA_REPORTER:=spec} test/*-test.js",
"coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls",
"lint": "eslint --reset -c eslint.json src/js/ripple/*.js",
"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/",
"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/",
"perf": "./scripts/perf_test.sh"
},
"repository": {
@@ -61,6 +67,6 @@
},
"readmeFilename": "README.md",
"engines": {
"node": ">=0.10.0"
"node": ">=0.12.0"
}
}

View File

@@ -8,4 +8,4 @@ then
mkdir -p "$DIR/cache"
curl -L "$URL" > "$DEST"
fi
time node "$DIR/verify_ledger_json.js" "$DEST"
npm run compile && time node "$DIR/verify_ledger_json.js" "$DEST"

View File

@@ -17,15 +17,15 @@ echo "publish to npm"
npm publish
exit_on_error
rm -rf dist
rm -rf dist/bower
echo ""
echo "publish to bower"
git clone git@github.com:ripple/bower-ripple.git dist
git clone git@github.com:ripple/bower-ripple.git dist/bower
gulp bower
exit_on_error
cd dist
cd dist/bower
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
echo "version: $version"
git add ripple.js ripple-debug.js ripple-min.js bower.json
@@ -40,4 +40,4 @@ exit_on_error
git push origin master
git push --tags origin master
cd ..
cd ..

View File

@@ -17,15 +17,15 @@ echo "publish rc to npm"
npm publish --tag beta
exit_on_error
rm -rf dist
rm -rf dist/bower
echo ""
echo "publish to bower"
git clone git@github.com:ripple/bower-ripple.git dist
git clone git@github.com:ripple/bower-ripple.git dist/bower
gulp bower
exit_on_error
cd dist
cd dist/bower
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
echo "version: $version"
git add ripple.js ripple-debug.js ripple-min.js bower.json
@@ -40,4 +40,4 @@ exit_on_error
git push origin master
git push --tags origin master
cd ..
cd ..

View File

@@ -1,7 +1,7 @@
rm -rf dist
git clone git@github.com:ripple/bower-ripple.git dist
rm -rf dist/bower
git clone git@github.com:ripple/bower-ripple.git dist/bower
gulp bower
cd dist
cd dist/bower
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
echo "version: $version"
git add ripple.js ripple-debug.js ripple-min.js bower.json
@@ -9,4 +9,4 @@ git commit -m "[TASK] add v$version"
git tag "v$version"
git push origin master
git push --tags origin master
cd ..
cd ..

View File

@@ -1,31 +1,35 @@
/* eslint-disable no-var */
'use strict';
var fs = require('fs');
var Ledger = require('../src/js/ripple/ledger').Ledger;
var Amount = require('../dist/npm').Amount;
var Ledger = require('../dist/npm').Ledger;
function parse_options(from, flags) {
var argv = from.slice(),
opts = {argv:argv};
opts_ = {argv: argv};
flags.forEach(function(f) {
// Do we have the flag?
var flag_index = argv.indexOf('--' + f);
// normalize the name of the flag
f = f.replace('-', '_');
// opts has Boolean value for normalized flag key
opts[f] = !!~flag_index;
if (opts[f]) {
var flag = f.replace('-', '_');
// opts_ has Boolean value for normalized flag key
opts_[flag] = flag_index !== -1;
if (opts_[flag]) {
// remove the flag from the argv
argv.splice(flag_index, 1);
}
});
return opts;
return opts_;
}
var opts = parse_options(process.argv.slice(2), // remove `node` and `this.js`
['sanity-test']);
if (opts.argv.length < 1) {
console.error("Usage: scripts/verify_ledger_json path/to/ledger.json");
console.error(" optional: --sanity-test (json>binary>json>binary)");
console.error('Usage: scripts/verify_ledger_json path/to/ledger.json');
console.error(' optional: --sanity-test (json>binary>json>binary)');
process.exit(1);
}
@@ -36,14 +40,27 @@ var ledger = Ledger.from_json(JSON.parse(json));
// before finally serializing for hashing. This is mostly to expose any issues
// with ripple-libs binary <--> json codecs.
if (opts.sanity_test) {
console.log("All accountState nodes will be processed from " +
"json->binary->json->binary. This may take some time " +
"with large ledgers.");
console.log('All accountState nodes will be processed from ' +
'json->binary->json->binary. This may take some time ' +
'with large ledgers.');
}
console.log("Transaction hash in header: " + ledger.ledger_json.transaction_hash);
console.log("Calculated transaction hash: " + ledger.calc_tx_hash().to_hex());
console.log("Account state hash in header: " + ledger.ledger_json.account_hash);
console.log("Calculated account state hash: " + ledger.calc_account_hash(
{sanity_test:opts.sanity_test})
.to_hex());
// To recompute the hashes of some ledgers, we must allow values that slipped in
// before strong policies were in place.
Amount.strict_mode = false;
console.log('Transaction hash in header: ' +
ledger.ledger_json.transaction_hash);
console.log('Calculated transaction hash: ' +
ledger.calc_tx_hash().to_hex());
console.log('Account state hash in header: ' +
ledger.ledger_json.account_hash);
if (ledger.ledger_json.accountState) {
console.log('Calculated account state hash: ' +
ledger.calc_account_hash({sanity_test: opts.sanity_test})
.to_hex());
} else {
console.log('Ledger has no accountState');
}

View File

@@ -0,0 +1,44 @@
'use strict';
const core = require('./utils').core;
const flagIndices = core.Transaction.set_clear_flags.AccountSet;
const flags = core.Remote.flags.account_root;
const AccountFlags = {
passwordSpent: flags.PasswordSpent,
requireDestinationTag: flags.RequireDestTag,
requireAuthorization: flags.RequireAuth,
disallowIncomingXRP: flags.DisallowXRP,
disableMasterKey: flags.DisableMaster,
noFreeze: flags.NoFreeze,
globalFreeze: flags.GlobalFreeze,
defaultRipple: flags.DefaultRipple
};
const AccountFlagIndices = {
requireDestinationTag: flagIndices.asfRequireDest,
requireAuthorization: flagIndices.asfRequireAuth,
disallowIncomingXRP: flagIndices.asfDisallowXRP,
disableMasterKey: flagIndices.asfDisableMaster,
enableTransactionIDTracking: flagIndices.asfAccountTxnID,
noFreeze: flagIndices.asfNoFreeze,
globalFreeze: flagIndices.asfGlobalFreeze,
defaultRipple: flagIndices.asfDefaultRipple
};
const AccountFields = {
EmailHash: {name: 'emailHash', encoding: 'hex',
length: 32, defaults: '0'},
WalletLocator: {name: 'walletLocator', encoding: 'hex',
length: 64, defaults: '0'},
WalletSize: {name: 'walletSize', defaults: 0},
MessageKey: {name: 'messageKey'},
Domain: {name: 'domain', encoding: 'hex'},
TransferRate: {name: 'transferRate', defaults: 0},
Signers: {name: 'signers'}
};
module.exports = {
AccountFields,
AccountFlagIndices,
AccountFlags
};

89
src/api/common/errors.js Normal file
View File

@@ -0,0 +1,89 @@
/* eslint-disable valid-jsdoc */
'use strict';
/**
* Base class for all errors
*/
function RippleError(message) {
this.message = message;
}
RippleError.prototype = new Error();
RippleError.prototype.name = 'RippleError';
function ValidationError(message) {
this.message = message;
}
ValidationError.prototype = new RippleError();
ValidationError.prototype.name = 'ValidationError';
/**
* Timeout, disconnects and too busy
*/
function NetworkError(message) {
this.message = message;
}
NetworkError.prototype = new RippleError();
NetworkError.prototype.name = 'NetworkError';
/**
* Failed transactions, no paths found, not enough balance, etc.
*/
function RippledNetworkError(message) {
this.message = message !== undefined ? message : 'Cannot connect to rippled';
}
RippledNetworkError.prototype = new NetworkError();
/**
* Failed transactions, no paths found, not enough balance, etc.
*/
function TransactionError(message) {
this.message = message;
}
TransactionError.prototype = new RippleError();
TransactionError.prototype.name = 'TransactionError';
/**
* Asset could not be found
*/
function NotFoundError(message) {
this.message = message;
}
NotFoundError.prototype = new RippleError();
NotFoundError.prototype.name = 'NotFoundError';
function MissingLedgerHistoryError(message) {
this.message = message ||
'Server is missing ledger history in the specified range';
}
MissingLedgerHistoryError.prototype = new RippleError();
MissingLedgerHistoryError.prototype.name = 'MissingLedgerHistoryError';
/**
* Request timed out
*/
function TimeOutError(message) {
this.message = message;
}
TimeOutError.prototype = new RippleError();
TimeOutError.prototype.name = 'TimeOutError';
/**
* API logic failed to do what it intended
*/
function ApiError(message) {
this.message = message;
}
ApiError.prototype = new RippleError();
ApiError.prototype.name = 'ApiError';
module.exports = {
ValidationError: ValidationError,
NetworkError: NetworkError,
TransactionError: TransactionError,
RippledNetworkError: RippledNetworkError,
NotFoundError: NotFoundError,
MissingLedgerHistoryError: MissingLedgerHistoryError,
TimeOutError: TimeOutError,
ApiError: ApiError,
RippleError: RippleError
};

15
src/api/common/index.js Normal file
View File

@@ -0,0 +1,15 @@
'use strict';
const utils = require('./utils');
module.exports = {
core: utils.core,
constants: require('./constants'),
errors: require('./errors'),
validate: require('./validate'),
dropsToXrp: utils.dropsToXrp,
xrpToDrops: utils.xrpToDrops,
toRippledAmount: utils.toRippledAmount,
wrapCatch: utils.wrapCatch,
composeAsync: utils.composeAsync,
convertExceptions: utils.convertExceptions
};

View File

@@ -0,0 +1,63 @@
'use strict';
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
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 isValidLedgerHash(ledgerHash) {
return core.UInt256.is_valid(ledgerHash);
}
function loadSchema(filepath) {
try {
return JSON.parse(fs.readFileSync(filepath, 'utf8'));
} catch (e) {
throw new Error('Failed to parse schema: ' + filepath);
}
}
function endsWith(str, suffix) {
return str.indexOf(suffix, str.length - suffix.length) !== -1;
}
function loadSchemas(dir) {
const filenames = fs.readdirSync(dir).filter(name => endsWith(name, '.json'));
const schemas = filenames.map(name => loadSchema(path.join(dir, name)));
return _.indexBy(schemas, 'title');
}
function formatSchemaError(error) {
return error.field + ' ' + error.message
+ (error.value ? ' (' + JSON.stringify(error.value) + ')' : '');
}
function formatSchemaErrors(errors) {
return errors.map(formatSchemaError).join(', ');
}
function schemaValidate(schemaName, object) {
const formats = {address: isValidAddress,
ledgerHash: isValidLedgerHash};
const options = {schemas: SCHEMAS, formats: formats,
verbose: true, greedy: true};
const schema = SCHEMAS[schemaName];
if (schema === undefined) {
throw new Error('schema not found for: ' + schemaName);
}
const validate = validator(schema, options);
const isValid = validate(object);
if (!isValid) {
throw new ValidationError(formatSchemaErrors(validate.errors));
}
}
SCHEMAS = loadSchemas(path.join(__dirname, './schemas'));
module.exports = schemaValidate;

View File

@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "address",
"description": "A Ripple account address",
"type": "string",
"format": "address",
"pattern": "^r[1-9A-HJ-NP-Za-km-z]{25,34}$"
}

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "adjustment",
"type": "object",
"properties": {
"address": {"$ref": "address"},
"amount": {"$ref": "amount"},
"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", "amount"],
"additionalProperties": false
}

View File

@@ -0,0 +1,9 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "amount",
"description": "An Amount on the Ripple Protocol, used also for XRP in the ripple-rest API",
"allOf": [
{"$ref": "amountbase"},
{"required": ["value"]}
]
}

View File

@@ -0,0 +1,44 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "amountbase",
"description": "Base class for amount and issue",
"type": "object",
"properties": {
"value": {
"description": "The quantity of the currency, denoted as a string to retain floating point precision",
"$ref": "value"
},
"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"],
"oneOf": [
{
"properties": {
"currency": {
"not": {
"enum": ["XRP"]
}
}
},
"required": ["counterparty"]
},
{
"properties": {
"currency": {
"enum": ["XRP"]
}
},
"not": {
"required": ["counterparty"]
}
}
]
}

View File

@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "blob",
"description": "An uppercase hex string representation of a transaction",
"type": "string",
"minLength": "1",
"pattern": "^[0-9A-F]*$"
}

View File

@@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "cancellation",
"type": "object",
"properties": {
"orderSequence": {"$ref": "sequence"}
},
"required": ["orderSequence"],
"additionalProperties": false
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "currency",
"description": "The three-character code or hex string used to denote currencies",
"type": "string",
"pattern": "^([a-zA-Z0-9<>(){}[\\]|?!@#$%^&*]{3}|[A-F0-9]{40})$"
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "hash128",
"description": "The hex representation of a 128-bit hash",
"type": "string",
"pattern": "^[A-F0-9]{32}$"
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "hash256",
"description": "The hex representation of a 256-bit hash",
"type": "string",
"pattern": "^[A-F0-9]{64}$"
}

View File

@@ -0,0 +1,42 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "instructions",
"description": "Instructions for executing a transaction",
"type": "object",
"properties": {
"sequence": {
"description": "The sequence number, relative to the initiating account, of this transaction.",
"$ref": "sequence"
},
"fee": {
"description": "Fixed Fee",
"$ref": "value"
},
"maxFee": {
"description": "Max Fee",
"$ref": "value"
},
"maxLedgerVersion": {
"description": "Highest ledger version number that a transaction can appear in.",
"$ref": "ledgerVersion"
},
"maxLedgerVersionOffset": {
"description": "Offset from current legder version to highest ledger version that a transaction can appear in.",
"type": "integer",
"minimum": 0
}
},
"additionalProperties": false,
"not": {
"anyOf": [
{
"description": "fee and maxFee are mutually exclusive",
"required": ["fee", "maxFee"]
},
{
"description": "maxLedgerVersion and maxLedgerVersionOffset are mutually exclusive",
"required": ["maxLedgerVersion", "maxLedgerVersionOffset"]
}
]
}
}

View File

@@ -0,0 +1,9 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "issue",
"description": "A currency-counterparty pair, or just currency if it's XRP",
"allOf": [
{"$ref": "amountbase"},
{"not": {"required": ["value"]}}
]
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "ledgerVersion",
"description": "A ledger version number",
"type": "integer",
"minimum": 1
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "memo",
"description": "Memo objects represent arbitrary data that can be included in a transaction",
"type": "object",
"properties": {
"type": {
"pattern": "^[A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=%]*$"
},
"format": {
"pattern": "^[A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=%]*$"
},
"data": {
"type": "string"
}
},
"additionalProperties": false,
"anyOf": [
{"required": ["data"]},
{"required": ["type"]}
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "order",
"type": "object",
"properties": {
"direction": {
"type": "string",
"enum": ["buy", "sell"]
},
"quantity": {"$ref": "amount"},
"totalPrice": {"$ref": "amount"},
"immediateOrCancel": {"type": "boolean"},
"fillOrKill": {"type": "boolean"},
"passive": {
"description": "If enabled, the offer will not consume offers that exactly match it, and instead becomes an Offer node in the ledger. It will still consume offers that cross it.",
"type": "boolean"
}
},
"required": ["direction", "quantity", "totalPrice"],
"additionalProperties": false,
"not": {
"description": "immediateOrCancel and fillOrKill are mutually exclusive",
"required": ["immediateOrCancel", "fillOrKill"]
}
}

View File

@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "orderbook",
"type": "object",
"properties": {
"base": {"$ref": "issue"},
"counter": {"$ref": "issue"}
},
"required": ["base", "counter"],
"additionalProperties": false
}

View File

@@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "orders-options",
"description": "Options for getOrders and getOrderbook",
"type": "object",
"properties": {
"limit": {
"type": "integer",
"minimum": 1
},
"ledgerVersion": {"$ref": "ledgerVersion"}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,31 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "pathfind",
"type": "object",
"properties": {
"source": {
"type": "object",
"properties": {
"address": {"$ref": "address"},
"currencies": {
"type": "array",
"items": {
"type": "object",
"properties": {
"currency": {"$ref": "currency"},
"counterparty": {"$ref": "address"}
},
"required": ["currency"],
"additionalProperties": false
},
"uniqueItems": true
}
},
"additionalProperties": false,
"required": ["address"]
},
"destination": {"$ref": "adjustment"}
},
"required": ["source", "destination"],
"additionalProperties": false
}

View File

@@ -0,0 +1,35 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "payment",
"type": "object",
"properties": {
"source": {"$ref": "adjustment"},
"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": {
"$ref": "memo"
}
},
"invoiceID": {
"description": "A 256-bit hash that can be used to identify a particular payment",
"$ref": "hash256"
},
"allowPartialPayment": {
"description": "A boolean that, if set to true, indicates that this payment should go through even if the whole amount cannot be delivered because of a lack of liquidity or funds in the source_account account",
"type": "boolean"
},
"noDirectRipple": {
"description": "A boolean that can be set to true if paths are specified and the sender would like the Ripple Network to disregard any direct paths from the source_account to the destination_account. This may be used to take advantage of an arbitrage opportunity or by gateways wishing to issue balances from a hot wallet to a user who has mistakenly set a trustline directly to the hot wallet",
"type": "boolean"
},
"limitQuality": {"type": "boolean"}
},
"required": ["source", "destination"],
"additionalProperties": false
}

View File

@@ -0,0 +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
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "sequence",
"description": "An account transaction sequence number",
"type": "integer",
"minimum": 1
}

View File

@@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "settings-options",
"description": "Options for getSettings and getAccountInfo",
"type": "object",
"properties": {
"ledgerVersion": {"$ref": "ledgerVersion"}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,27 @@
{
"$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
}

View File

@@ -0,0 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "timestamp",
"description": "An ISO 8601 combined date and time timestamp",
"type": "string",
"pattern": "^$|^[0-9]{4}-[0-1][0-9]-[0-3][0-9]T(2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9](Z|[+](2[0-3]|[01][0-9]):[0-5][0-9])$"
}

View File

@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "transaction-options",
"description": "Options for getTransaction",
"type": "object",
"properties": {
"minLedgerVersion": {"$ref": "ledgerVersion"},
"maxLedgerVersion": {"$ref": "ledgerVersion"}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "transactions-options",
"description": "Options for getTransactions",
"type": "object",
"properties": {
"start": {"$ref": "hash256"},
"limit": {
"type": "integer",
"minimum": 1
},
"minLedgerVersion": {"$ref": "ledgerVersion"},
"maxLedgerVersion": {"$ref": "ledgerVersion"},
"earliestFirst": {"type": "boolean"},
"excludeFailures": {"type": "boolean"},
"initiated": {"type": "boolean"},
"counterparty": {"$ref": "address"},
"types": {
"type": "array",
"items": {
"enum": [
"payment",
"trustline",
"order",
"orderCancellation",
"settings"
]
}
},
"binary": {"type": "boolean"}
},
"additionalProperties": false,
"not": {
"anyOf": [
{"required": ["incoming", "outgoing"]},
{"required": ["start", "minLedgerVersion"]},
{"required": ["start", "maxLedgerVersion"]}
]
}
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "trustline",
"type": "object",
"properties": {
"currency": {"$ref": "currency"},
"counterparty": {"$ref": "address"},
"limit": {"$ref": "value"},
"qualityIn": {"$ref": "quality"},
"qualityOut": {"$ref": "quality"},
"allowRippling": {"type": "boolean"},
"authorized": {"type": "boolean"},
"frozen": {"type": "boolean"}
},
"required": ["currency", "counterparty", "limit"],
"additionalProperties": false
}

View File

@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "trustlines-options",
"description": "Options for getTrustlines and getBalances",
"type": "object",
"properties": {
"counterparty": {"$ref": "address"},
"currency": {"$ref": "currency"},
"limit": {
"type": "integer",
"minimum": 1
},
"ledgerVersion": {"$ref": "ledgerVersion"}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,10 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "tx",
"description": "An object in rippled txJSON format",
"type": "object",
"properties": {
"Account": {"$ref": "address"}
},
"required": ["Account"]
}

View File

@@ -0,0 +1,8 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "uint32",
"description": "A 32-bit unsigned integer",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
}

View File

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

71
src/api/common/utils.js Normal file
View File

@@ -0,0 +1,71 @@
'use strict';
const BigNumber = require('bignumber.js');
const core = require('../../core');
const errors = require('./errors');
function dropsToXrp(drops) {
return (new BigNumber(drops)).dividedBy(1000000.0).toString();
}
function xrpToDrops(xrp) {
return (new BigNumber(xrp)).times(1000000.0).floor().toString();
}
function toRippledAmount(amount) {
if (amount.currency === 'XRP') {
return xrpToDrops(amount.value);
}
return {
currency: amount.currency,
issuer: amount.counterparty ? amount.counterparty : amount.issuer,
value: amount.value
};
}
function wrapCatch(asyncFunction: () => void): () => void {
return function() {
try {
asyncFunction.apply(this, arguments);
} catch (error) {
const callback = arguments[arguments.length - 1];
callback(error);
}
};
}
function composeAsync(wrapper, callback) {
return function(error, data) {
if (error) {
callback(error);
return;
}
let result;
try {
result = wrapper(data);
} catch (exception) {
callback(exception);
return;
}
callback(null, result);
};
}
function convertExceptions(f) {
return function() {
try {
return f.apply(this, arguments);
} catch (error) {
throw new errors.ApiError(error.message);
}
};
}
module.exports = {
core,
dropsToXrp,
xrpToDrops,
toRippledAmount,
wrapCatch,
composeAsync,
convertExceptions
};

View File

@@ -0,0 +1,65 @@
'use strict';
const _ = require('lodash');
const core = require('./utils').core;
const ValidationError = require('./errors').ValidationError;
const schemaValidate = require('./schema-validator');
function error(text) {
return new ValidationError(text);
}
function validateAddressAndSecret(obj) {
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');
}
}
function validateLedgerRange(options) {
if (!_.isUndefined(options.minLedgerVersion)
&& !_.isUndefined(options.maxLedgerVersion)) {
if (Number(options.minLedgerVersion) > Number(options.maxLedgerVersion)) {
throw error('minLedgerVersion must not be greater than maxLedgerVersion');
}
}
}
function validateOptions(schema, options) {
schemaValidate(schema, options);
validateLedgerRange(options);
}
module.exports = {
address: _.partial(schemaValidate, 'address'),
addressAndSecret: validateAddressAndSecret,
currency: _.partial(schemaValidate, 'currency'),
identifier: _.partial(schemaValidate, 'hash256'),
sequence: _.partial(schemaValidate, 'sequence'),
order: _.partial(schemaValidate, 'order'),
orderbook: _.partial(schemaValidate, 'orderbook'),
payment: _.partial(schemaValidate, 'payment'),
pathfind: _.partial(schemaValidate, 'pathfind'),
settings: _.partial(schemaValidate, 'settings'),
trustline: _.partial(schemaValidate, 'trustline'),
txJSON: _.partial(schemaValidate, 'tx'),
blob: _.partial(schemaValidate, 'blob'),
getTransactionsOptions: _.partial(validateOptions, 'transactions-options'),
getSettingsOptions: _.partial(validateOptions, 'settings-options'),
getAccountInfoOptions: _.partial(validateOptions, 'settings-options'),
getTrustlinesOptions: _.partial(validateOptions, 'trustlines-options'),
getBalancesOptions: _.partial(validateOptions, 'trustlines-options'),
getOrdersOptions: _.partial(validateOptions, 'orders-options'),
getOrderbookOptions: _.partial(validateOptions, 'orders-options'),
getTransactionOptions: _.partial(validateOptions, 'transaction-options'),
options: _.partial(validateOptions, 'options'),
instructions: _.partial(schemaValidate, 'instructions')
};

68
src/api/index.js Normal file
View File

@@ -0,0 +1,68 @@
/* @flow */
'use strict';
const _ = require('lodash');
const core = require('./common').core;
const server = require('./server/server');
const connect = server.connect;
const disconnect = server.disconnect;
const getServerInfo = server.getServerInfo;
const getFee = server.getFee;
const isConnected = server.isConnected;
const getLedgerVersion = server.getLedgerVersion;
const getTransaction = require('./ledger/transaction');
const getTransactions = require('./ledger/transactions');
const getTrustlines = require('./ledger/trustlines');
const getBalances = require('./ledger/balances');
const getPaths = require('./ledger/pathfind');
const getOrders = require('./ledger/orders');
const getOrderbook = require('./ledger/orderbook');
const getSettings = require('./ledger/settings');
const getAccountInfo = require('./ledger/accountinfo');
const preparePayment = require('./transaction/payment');
const prepareTrustline = require('./transaction/trustline');
const prepareOrder = require('./transaction/order');
const prepareOrderCancellation = require('./transaction/ordercancellation');
const prepareSettings = require('./transaction/settings');
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);
function RippleAPI(options: {}) {
const _options = _.assign({}, options, {automatic_resubmission: false});
this.remote = new core.Remote(_options);
}
RippleAPI.prototype = {
connect,
disconnect,
isConnected,
getServerInfo,
getFee,
getLedgerVersion,
getTransaction,
getTransactions,
getTrustlines,
getBalances,
getPaths,
getOrders,
getOrderbook,
getSettings,
getAccountInfo,
preparePayment,
prepareTrustline,
prepareOrder,
prepareOrderCancellation,
prepareSettings,
sign,
submit,
generateWallet,
errors
};
module.exports = RippleAPI;

View File

@@ -0,0 +1,34 @@
/* @flow */
'use strict';
const utils = require('./utils');
const removeUndefined = require('./parse/utils').removeUndefined;
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
function formatAccountInfo(response) {
const data = response.account_data;
return removeUndefined({
sequence: data.Sequence,
xrpBalance: utils.common.dropsToXrp(data.Balance),
ownerCount: data.OwnerCount,
previousInitiatedTransactionID: data.AccountTxnID,
previousAffectingTransactionID: data.PreviousTxnID,
previousAffectingTransactionLedgerVersion: data.PreviousTxnLgrSeq
});
}
function getAccountInfo(account, options, callback) {
validate.address(account);
validate.getAccountInfoOptions(options);
const request = {
account: account,
ledger: options.ledgerVersion
};
this.remote.requestAccountInfo(request,
composeAsync(formatAccountInfo, callback));
}
module.exports = utils.wrapCatch(getAccountInfo);

View File

@@ -0,0 +1,39 @@
/* @flow */
'use strict';
const _ = require('lodash');
const async = require('async');
const utils = require('./utils');
const getTrustlines = require('./trustlines');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
function getTrustlineBalanceAmount(trustline) {
return {
currency: trustline.specification.currency,
counterparty: trustline.specification.counterparty,
value: trustline.state.balance
};
}
function formatBalances(balances) {
const xrpBalance = {
currency: 'XRP',
value: balances.xrp
};
return [xrpBalance].concat(
balances.trustlines.map(getTrustlineBalanceAmount));
}
function getBalances(account, options, callback) {
validate.address(account);
validate.getBalancesOptions(options);
const ledgerVersion = options.ledgerVersion
|| this.remote.getLedgerSequence();
async.parallel({
xrp: _.partial(utils.getXRPBalance, this.remote, account, ledgerVersion),
trustlines: _.partial(getTrustlines.bind(this), account, options)
}, composeAsync(formatBalances, callback));
}
module.exports = utils.wrapCatch(getBalances);

View File

@@ -0,0 +1,79 @@
/* @flow */
'use strict';
const _ = require('lodash');
const async = require('async');
const utils = require('./utils');
const parseOrderbookOrder = require('./parse/orderbook-order');
const validate = utils.common.validate;
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) {
remote.requestBookOffers(utils.renameCounterpartyToIssuerInOrder({
taker_gets: takerGets,
taker_pays: takerPays,
ledger: ledgerVersion || 'validated',
limit: limit,
taker: account
}), composeAsync(data => data.offers, callback));
}
function isSameIssue(a, b) {
return a.currency === b.currency && a.counterparty === b.counterparty;
}
function directionFilter(direction, order) {
return order.specification.direction === direction;
}
function flipOrder(order) {
const specification = order.specification;
const flippedSpecification = {
quantity: specification.totalPrice,
totalPrice: specification.quantity,
direction: specification.direction === 'buy' ? 'sell' : 'buy'
};
const newSpecification = _.merge({}, specification, flippedSpecification);
return _.merge({}, order, {specification: newSpecification});
}
function alignOrder(base, order) {
const quantity = order.specification.quantity;
return isSameIssue(quantity, base) ? order : flipOrder(order);
}
function formatBidsAndAsks(orderbook, offers) {
// the "base" currency is the currency that you are buying or selling
// the "counter" is the currency that the "base" is priced in
// a "bid"/"ask" is an order to buy/sell the base, respectively
// for bids: takerGets = totalPrice = counter, takerPays = quantity = base
// for asks: takerGets = quantity = base, takerPays = totalPrice = counter
// quality = takerPays / takerGets; price = totalPrice / quantity
// for bids: lowest quality => lowest quantity/totalPrice => highest price
// for asks: lowest quality => lowest totalPrice/quantity => lowest price
// for both bids and asks, lowest quality is closest to mid-market
// we sort the orders so that earlier orders are closer to mid-market
const orders = _.sortBy(offers, 'quality').map(parseOrderbookOrder);
const alignedOrders = orders.map(_.partial(alignOrder, orderbook.base));
const bids = alignedOrders.filter(_.partial(directionFilter, 'buy'));
const asks = alignedOrders.filter(_.partial(directionFilter, 'sell'));
return {bids, asks};
}
function getOrderbook(account, orderbook, options, callback) {
validate.address(account);
validate.orderbook(orderbook);
validate.getOrderbookOptions(options);
const getter = _.partial(getBookOffers, this.remote, account,
options.ledgerVersion, options.limit);
const getOffers = _.partial(getter, orderbook.base, orderbook.counter);
const getReverseOffers = _.partial(getter, orderbook.counter, orderbook.base);
async.parallel([getOffers, getReverseOffers],
composeAsync((data) => formatBidsAndAsks(orderbook, _.flatten(data)),
callback));
}
module.exports = utils.wrapCatch(getOrderbook);

36
src/api/ledger/orders.js Normal file
View File

@@ -0,0 +1,36 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const parseAccountOrder = require('./parse/account-order');
function requestAccountOffers(remote, address, ledgerVersion, options,
marker, limit, callback) {
remote.requestAccountOffers({
account: address,
marker: marker,
limit: utils.clamp(limit, 10, 400),
ledger: ledgerVersion
},
composeAsync((data) => ({
marker: data.marker,
results: data.offers.map(_.partial(parseAccountOrder, address))
}), callback));
}
function getOrders(account, options, callback) {
validate.address(account);
validate.getOrdersOptions(options);
const ledgerVersion = options.ledgerVersion
|| this.remote.getLedgerSequence();
const getter = _.partial(requestAccountOffers, this.remote, account,
ledgerVersion, options);
utils.getRecursive(getter, options.limit,
composeAsync((orders) => _.sortBy(orders,
(order) => order.properties.sequence), callback));
}
module.exports = utils.wrapCatch(getOrders);

View File

@@ -0,0 +1,41 @@
/* @flow */
'use strict';
const utils = require('./utils');
const flags = utils.core.Remote.flags.offer;
const parseAmount = require('./amount');
const BigNumber = require('bignumber.js');
// TODO: remove this function once rippled provides quality directly
function computeQuality(takerGets, takerPays) {
const quotient = new BigNumber(takerPays.value).dividedBy(takerGets.value);
return quotient.toDigits(16, BigNumber.ROUND_HALF_UP).toString();
}
// rippled 'account_offers' returns a different format for orders than 'tx'
// the flags are also different
function parseAccountOrder(address: string, order: Object): Object {
const direction = (order.flags & flags.Sell) === 0 ? 'buy' : 'sell';
const takerGetsAmount = parseAmount(order.taker_gets);
const takerPaysAmount = parseAmount(order.taker_pays);
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
// so we can omit those flags here
const specification = utils.removeUndefined({
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
passive: ((order.flags & flags.Passive) !== 0) || undefined
});
const properties = {
maker: address,
sequence: order.seq,
makerExchangeRate: computeQuality(takerGetsAmount, takerPaysAmount)
};
return {specification, properties};
}
module.exports = parseAccountOrder;

View File

@@ -0,0 +1,46 @@
/* @flow */
'use strict';
const utils = require('./utils');
type Trustline = {
account: string, limit: number, currency: string, quality_in: ?number,
quality_out: ?number, no_ripple: boolean, freeze: boolean, authorized: boolean,
limit_peer: string, no_ripple_peer: boolean, freeze_peer: boolean,
peer_authorized: boolean, balance: any
}
type TrustlineSpecification = {}
type TrustlineCounterParty = {}
type TrustlineState = {balance: number}
type AccountTrustline = {
specification: TrustlineSpecification, counterparty: TrustlineCounterParty,
state: TrustlineState
}
// rippled 'account_lines' returns a different format for
// trustlines than 'tx'
function parseAccountTrustline(trustline: Trustline): AccountTrustline {
const specification = utils.removeUndefined({
limit: trustline.limit,
currency: trustline.currency,
counterparty: trustline.account,
qualityIn: trustline.quality_in || undefined,
qualityOut: trustline.quality_out || undefined,
ripplingDisabled: trustline.no_ripple || undefined,
frozen: trustline.freeze || undefined,
authorized: trustline.authorized || undefined
});
// rippled doesn't provide the counterparty's qualities
const counterparty = utils.removeUndefined({
limit: trustline.limit_peer,
ripplingDisabled: trustline.no_ripple_peer || undefined,
frozen: trustline.freeze_peer || undefined,
authorized: trustline.peer_authorized || undefined
});
const state = {
balance: trustline.balance
};
return {specification, counterparty, state};
}
module.exports = parseAccountTrustline;

View File

@@ -0,0 +1,24 @@
/* @flow */
'use strict';
const utils = require('./utils');
type Amount = string | {currency: string, issuer: string, value: string}
type XRPAmount = {currency: string, value: string}
type IOUAmount = {currency: string, value: string, counterparty: string}
type Output = XRPAmount | IOUAmount
function parseAmount(amount: Amount): Output {
if (typeof amount === 'string') {
return {
currency: 'XRP',
value: utils.dropsToXrp(amount)
};
}
return {
currency: amount.currency,
value: amount.value,
counterparty: amount.issuer
};
}
module.exports = parseAmount;

View File

@@ -0,0 +1,12 @@
/* @flow */
'use strict';
const assert = require('assert');
function parseOrderCancellation(tx: Object): Object {
assert(tx.TransactionType === 'OfferCancel');
return {
orderSequence: tx.OfferSequence
};
}
module.exports = parseOrderCancellation;

View File

@@ -0,0 +1,24 @@
/* @flow */
'use strict';
const AccountFields = require('./utils').constants.AccountFields;
function parseField(info, value) {
if (info.encoding === 'hex' && !info.length) {
return new Buffer(value, 'hex').toString('ascii');
}
return value;
}
function parseFields(data: Object): Object {
const settings = {};
for (const fieldName in AccountFields) {
const fieldValue = data[fieldName];
if (fieldValue !== undefined) {
const info = AccountFields[fieldName];
settings[info.name] = parseField(info, fieldValue);
}
}
return settings;
}
module.exports = parseFields;

View File

@@ -0,0 +1,28 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
const parseAmount = require('./amount');
const flags = utils.core.Transaction.flags.OfferCreate;
function parseOrder(tx: Object): Object {
assert(tx.TransactionType === 'OfferCreate');
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell';
const takerGetsAmount = parseAmount(tx.TakerGets);
const takerPaysAmount = parseAmount(tx.TakerPays);
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
return utils.removeUndefined({
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
passive: ((tx.Flags & flags.Passive) !== 0) || undefined,
immediateOrCancel: ((tx.Flags & flags.ImmediateOrCancel) !== 0)
|| undefined,
fillOrKill: ((tx.Flags & flags.FillOrKill) !== 0) || undefined
});
}
module.exports = parseOrder;

View File

@@ -0,0 +1,43 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const flags = utils.core.Remote.flags.offer;
const parseAmount = require('./amount');
function parseOrderbookOrder(order: Object): Object {
const direction = (order.Flags & flags.Sell) === 0 ? 'buy' : 'sell';
const takerGetsAmount = parseAmount(order.TakerGets);
const takerPaysAmount = parseAmount(order.TakerPays);
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
// so we can omit those flags here
const specification = utils.removeUndefined({
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
passive: ((order.Flags & flags.Passive) !== 0) || undefined
});
const properties = {
maker: order.Account,
sequence: order.Sequence,
makerExchangeRate: utils.adjustQualityForXRP(order.quality,
takerGetsAmount.currency, takerPaysAmount.currency)
};
const takerGetsFunded = order.taker_gets_funded ?
parseAmount(order.taker_gets_funded) : undefined;
const takerPaysFunded = order.taker_pays_funded ?
parseAmount(order.taker_pays_funded) : undefined;
const available = utils.removeUndefined({
fundedAmount: takerGetsFunded,
priceOfFundedAmount: takerPaysFunded
});
const state = _.isEmpty(available) ? undefined : available;
return utils.removeUndefined({specification, properties, state});
}
module.exports = parseOrderbookOrder;

View File

@@ -0,0 +1,28 @@
/* @flow */
'use strict';
const _ = require('lodash');
const parseAmount = require('./amount');
function parsePaths(paths) {
return paths.map(steps => steps.map(step =>
_.omit(step, ['type', 'type_hex'])));
}
function parsePathfind(sourceAddress: string,
destinationAmount: Object, pathfindResult: Object): Object {
return pathfindResult.alternatives.map(function(alternative) {
return {
source: {
address: sourceAddress,
amount: parseAmount(alternative.source_amount)
},
destination: {
address: pathfindResult.destination_account,
amount: destinationAmount
},
paths: JSON.stringify(parsePaths(alternative.paths_computed))
};
});
}
module.exports = parsePathfind;

View File

@@ -0,0 +1,60 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
const parseAmount = require('./amount');
const Transaction = utils.core.Transaction;
function isPartialPayment(tx) {
return (tx.Flags & Transaction.flags.Payment.PartialPayment) !== 0;
}
function isNoDirectRipple(tx) {
return (tx.Flags & Transaction.flags.Payment.NoRippleDirect) !== 0;
}
function isQualityLimited(tx) {
return (tx.Flags & Transaction.flags.Payment.LimitQuality) !== 0;
}
function parsePaymentMemos(tx) {
if (!Array.isArray(tx.Memos) || tx.Memos.length === 0) {
return undefined;
}
return tx.Memos.map((m) => {
return utils.removeUndefined({
type: m.Memo.parsed_memo_type,
format: m.Memo.parsed_memo_format,
data: m.Memo.parsed_memo_data
});
});
}
function parsePayment(tx: Object): Object {
assert(tx.TransactionType === 'Payment');
const source = {
address: tx.Account,
amount: parseAmount(tx.SendMax || tx.Amount),
tag: tx.SourceTag
};
const destination = {
address: tx.Destination,
amount: parseAmount(tx.Amount),
tag: tx.DestinationTag
};
return utils.removeUndefined({
source: utils.removeUndefined(source),
destination: utils.removeUndefined(destination),
memos: parsePaymentMemos(tx),
invoiceID: tx.InvoiceID,
paths: tx.Paths ? JSON.stringify(tx.Paths) : undefined,
allowPartialPayment: isPartialPayment(tx) || undefined,
noDirectRipple: isNoDirectRipple(tx) || undefined,
limitQuality: isQualityLimited(tx) || undefined
});
}
module.exports = parsePayment;

View File

@@ -0,0 +1,61 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const AccountFlags = require('./utils').constants.AccountFlags;
const parseFields = require('./fields');
function getAccountRootModifiedNode(tx: Object) {
const modifiedNodes = tx.meta.AffectedNodes.filter(node =>
node.ModifiedNode.LedgerEntryType === 'AccountRoot');
assert(modifiedNodes.length === 1);
return modifiedNodes[0].ModifiedNode;
}
function parseFlags(tx: Object) {
const settings = {};
if (tx.TransactionType !== 'AccountSet') {
return settings;
}
const node = getAccountRootModifiedNode(tx);
const oldFlags = _.get(node.PreviousFields, 'Flags');
const newFlags = _.get(node.FinalFields, 'Flags');
if (oldFlags !== undefined && newFlags !== undefined) {
const changedFlags = oldFlags ^ newFlags;
const setFlags = newFlags & changedFlags;
const clearedFlags = oldFlags & changedFlags;
_.forEach(AccountFlags, (flagValue, flagName) => {
if (setFlags & flagValue) {
settings[flagName] = true;
} else if (clearedFlags & flagValue) {
settings[flagName] = false;
}
});
}
// enableTransactionIDTracking requires a special case because it
// does not affect the Flags field; instead it adds/removes a field called
// "AccountTxnID" to/from the account root.
const oldField = _.get(node.PreviousFields, 'AccountTxnID');
const newField = _.get(node.FinalFields, 'AccountTxnID');
if (newField && !oldField) {
settings.enableTransactionIDTracking = true;
} else if (oldField && !newField) {
settings.enableTransactionIDTracking = false;
}
return settings;
}
function parseSettings(tx: Object) {
const txType = tx.TransactionType;
assert(txType === 'AccountSet' || txType === 'SetRegularKey');
const regularKey = tx.RegularKey ? {regularKey: tx.RegularKey} : {};
return _.assign(regularKey, parseFlags(tx), parseFields(tx));
}
module.exports = parseSettings;

View File

@@ -0,0 +1,46 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
const parsePayment = require('./payment');
const parseTrustline = require('./trustline');
const parseOrder = require('./order');
const parseOrderCancellation = require('./cancellation');
const parseSettings = require('./settings');
function parseTransactionType(type) {
const mapping = {
Payment: 'payment',
TrustSet: 'trustline',
OfferCreate: 'order',
OfferCancel: 'orderCancellation',
AccountSet: 'settings',
SetRegularKey: 'settings'
};
return mapping[type] || null;
}
function parseTransaction(tx: Object): Object {
const type = parseTransactionType(tx.TransactionType);
const mapping = {
'payment': parsePayment,
'trustline': parseTrustline,
'order': parseOrder,
'orderCancellation': parseOrderCancellation,
'settings': parseSettings
};
const parser = mapping[type];
assert(parser !== undefined, 'Unrecognized transaction type');
const specification = parser(tx);
const outcome = utils.parseOutcome(tx);
return utils.removeUndefined({
type: type,
address: tx.Account,
sequence: tx.Sequence,
id: tx.hash,
specification: utils.removeUndefined(specification),
outcome: outcome ? utils.removeUndefined(outcome) : undefined
});
}
module.exports = parseTransaction;

View File

@@ -0,0 +1,33 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
const flags = utils.core.Transaction.flags.TrustSet;
function parseFlag(flagsValue, trueValue, falseValue) {
if (flagsValue & trueValue) {
return true;
}
if (flagsValue & falseValue) {
return false;
}
return undefined;
}
function parseTrustline(tx: Object): Object {
assert(tx.TransactionType === 'TrustSet');
return utils.removeUndefined({
limit: tx.LimitAmount.value,
currency: tx.LimitAmount.currency,
counterparty: tx.LimitAmount.issuer,
qualityIn: tx.QualityIn,
qualityOut: tx.QualityOut,
ripplingDisabled: parseFlag(
tx.Flags, flags.SetNoRipple, flags.ClearNoRipple),
frozen: parseFlag(tx.Flags, flags.SetFreeze, flags.ClearFreeze),
authorized: parseFlag(tx.Flags, flags.SetAuth, 0)
});
}
module.exports = parseTrustline;

View File

@@ -0,0 +1,76 @@
/* @flow */
'use strict';
const _ = require('lodash');
const transactionParser = require('ripple-lib-transactionparser');
const toTimestamp = require('../../../core/utils').toTimestamp;
const utils = require('../utils');
const BigNumber = require('bignumber.js');
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);
const denominatorShift = (takerGetsCurrency === 'XRP' ? -6 : 0);
const shift = numeratorShift - denominatorShift;
return shift === 0 ? quality :
(new BigNumber(quality)).shift(shift).toString();
}
function parseTimestamp(tx: {date: string}): string | void {
return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined;
}
function removeUndefined(obj: Object): Object {
return _.omit(obj, _.isUndefined);
}
function removeEmptyCounterparty(amount) {
if (amount.counterparty === '') {
delete amount.counterparty;
}
}
function removeEmptyCounterpartyInBalanceChanges(balanceChanges) {
_.forEach(balanceChanges, (changes) => {
_.forEach(changes, removeEmptyCounterparty);
});
}
function removeEmptyCounterpartyInOrderbookChanges(orderbookChanges) {
_.forEach(orderbookChanges, (changes) => {
_.forEach(changes, (change) => {
_.forEach(change, removeEmptyCounterparty);
});
});
}
function parseOutcome(tx: Object): ?Object {
if (!tx.validated) {
return undefined;
}
const balanceChanges = transactionParser.parseBalanceChanges(tx.meta);
const orderbookChanges = transactionParser.parseOrderBookChanges(tx.meta);
removeEmptyCounterpartyInBalanceChanges(balanceChanges);
removeEmptyCounterpartyInOrderbookChanges(orderbookChanges);
return {
result: tx.meta.TransactionResult,
timestamp: parseTimestamp(tx),
fee: utils.common.dropsToXrp(tx.Fee),
balanceChanges: balanceChanges,
orderbookChanges: orderbookChanges,
ledgerVersion: tx.ledger_index,
indexInLedger: tx.meta.TransactionIndex
};
}
module.exports = {
parseOutcome,
removeUndefined,
adjustQualityForXRP,
dropsToXrp: utils.common.dropsToXrp,
constants: utils.common.constants,
core: utils.common.core
};

116
src/api/ledger/pathfind.js Normal file
View File

@@ -0,0 +1,116 @@
/* @flow */
'use strict';
const _ = require('lodash');
const async = require('async');
const BigNumber = require('bignumber.js');
const utils = require('./utils');
const validate = utils.common.validate;
const parsePathfind = require('./parse/pathfind');
const NotFoundError = utils.common.errors.NotFoundError;
const composeAsync = utils.common.composeAsync;
type PathFindParams = {
src_currencies?: Array<string>, src_account: string, dst_amount: string,
dst_account?: string
}
function addParams(params: PathFindParams, result: {}) {
return _.assign({}, result, {
source_account: params.src_account,
source_currencies: params.src_currencies,
destination_amount: params.dst_amount
});
}
type PathFind = {
source: {address: string, currencies: Array<string>},
destination: {address: string, amount: string}
}
function requestPathFind(remote, pathfind: PathFind, callback) {
const params: PathFindParams = {
src_account: pathfind.source.address,
dst_account: pathfind.destination.address,
dst_amount: utils.common.toRippledAmount(pathfind.destination.amount)
};
if (typeof params.dst_amount === 'object' && !params.dst_amount.issuer) {
// Convert blank issuer to sender's address
// (Ripple convention for 'any issuer')
// https://ripple.com/build/transactions/
// #special-issuer-values-for-sendmax-and-amount
// https://ripple.com/build/ripple-rest/#counterparties-in-payments
params.dst_amount.issuer = params.dst_account;
}
if (pathfind.source.currencies && pathfind.source.currencies.length > 0) {
params.src_currencies = pathfind.source.currencies.map(amount =>
_.omit(utils.common.toRippledAmount(amount), 'value'));
}
remote.requestRipplePathFind(params,
composeAsync(_.partial(addParams, params), callback));
}
function addDirectXrpPath(paths, xrpBalance) {
// Add XRP "path" only if the source acct has enough XRP to make the payment
const destinationAmount = paths.destination_amount;
if ((new BigNumber(xrpBalance)).greaterThanOrEqualTo(destinationAmount)) {
paths.alternatives.unshift({
paths_computed: [],
source_amount: paths.destination_amount
});
}
return paths;
}
function isRippledIOUAmount(amount) {
// rippled XRP amounts are specified as decimal strings
return (typeof amount === 'object') &&
amount.currency && (amount.currency !== 'XRP');
}
function conditionallyAddDirectXRPPath(remote, address, paths, callback) {
if (isRippledIOUAmount(paths.destination_amount)
|| !_.includes(paths.destination_currencies, 'XRP')) {
callback(null, paths);
} else {
utils.getXRPBalance(remote, address, undefined,
composeAsync(_.partial(addDirectXrpPath, paths), callback));
}
}
function formatResponse(pathfind, paths) {
if (paths.alternatives && paths.alternatives.length > 0) {
const address = pathfind.source.address;
return parsePathfind(address, pathfind.destination.amount, paths);
}
if (!_.includes(paths.destination_currencies,
pathfind.destination.amount.currency)) {
throw new NotFoundError('No paths found. ' +
'The destination_account does not accept ' +
pathfind.destination.amount.currency + ', they only accept: ' +
paths.destination_currencies.join(', '));
} else if (paths.source_currencies && paths.source_currencies.length > 0) {
throw new NotFoundError('No paths found. Please ensure' +
' that the source_account has sufficient funds to execute' +
' the payment in one of the specified source_currencies. If it does' +
' there may be insufficient liquidity in the network to execute' +
' this payment right now');
} else {
throw new NotFoundError('No paths found.' +
' Please ensure that the source_account has sufficient funds to' +
' execute the payment. If it does there may be insufficient liquidity' +
' in the network to execute this payment right now');
}
}
function getPaths(pathfind, callback) {
validate.pathfind(pathfind);
const address = pathfind.source.address;
async.waterfall([
_.partial(requestPathFind, this.remote, pathfind),
_.partial(conditionallyAddDirectXRPPath, this.remote, address)
], composeAsync(_.partial(formatResponse, pathfind), callback));
}
module.exports = utils.wrapCatch(getPaths);

View File

@@ -0,0 +1,40 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const parseFields = require('./parse/fields');
const composeAsync = utils.common.composeAsync;
const AccountFlags = utils.common.constants.AccountFlags;
function parseFlags(value) {
const settings = {};
for (const flagName in AccountFlags) {
if (value & AccountFlags[flagName]) {
settings[flagName] = true;
}
}
return settings;
}
function formatSettings(response) {
const data = response.account_data;
const parsedFlags = parseFlags(data.Flags);
const parsedFields = parseFields(data);
return _.assign({}, parsedFlags, parsedFields);
}
function getSettings(account, options, callback) {
validate.address(account);
validate.getSettingsOptions(options);
const request = {
account: account,
ledger: options.ledgerVersion
};
this.remote.requestAccountInfo(request,
composeAsync(formatSettings, callback));
}
module.exports = utils.wrapCatch(getSettings);

View File

@@ -0,0 +1,67 @@
/* @flow */
'use strict';
const _ = require('lodash');
const async = require('async');
const utils = require('./utils');
const parseTransaction = require('./parse/transaction');
const validate = utils.common.validate;
const errors = utils.common.errors;
function attachTransactionDate(remote, tx, callback) {
if (tx.date) {
callback(null, tx);
return;
}
if (!tx.ledger_index) {
callback(new errors.NotFoundError('ledger_index not found in tx'));
return;
}
remote.requestLedger(tx.ledger_index, (error, data) => {
if (error) {
callback(new errors.NotFoundError('Transaction ledger not found'));
} else if (typeof data.ledger.close_time === 'number') {
callback(null, _.assign({date: data.ledger.close_time}, tx));
} else {
callback(new errors.ApiError('Ledger missing close_time'));
}
});
}
function isTransactionInRange(tx, options) {
return (!options.minLedgerVersion
|| tx.ledger_index >= options.minLedgerVersion)
&& (!options.maxLedgerVersion
|| tx.ledger_index <= options.maxLedgerVersion);
}
function getTransaction(identifier, options, callback) {
validate.identifier(identifier);
validate.getTransactionOptions(options);
const remote = this.remote;
const maxLedgerVersion = Math.min(options.maxLedgerVersion,
remote.getLedgerSequence());
function callbackWrapper(error, tx) {
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)) {
callback(new errors.NotFoundError('Transaction not found'));
} else if (error) {
callback(error);
} else {
callback(error, parseTransaction(tx));
}
}
async.waterfall([
_.partial(remote.requestTx.bind(remote), {hash: identifier, binary: false}),
_.partial(attachTransactionDate, remote)
], callbackWrapper);
}
module.exports = utils.wrapCatch(getTransaction);

View File

@@ -0,0 +1,123 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const parseTransaction = require('./parse/transaction');
const getTransaction = require('./transaction');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
function parseAccountTxTransaction(tx) {
// rippled uses a different response format for 'account_tx' than 'tx'
tx.tx.meta = tx.meta;
tx.tx.validated = tx.validated;
return parseTransaction(tx.tx);
}
function transactionFilter(address, filters, tx) {
if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') {
return false;
}
if (filters.types && !_.includes(filters.types, tx.type)) {
return false;
}
if (filters.initiated === true && tx.address !== address) {
return false;
}
if (filters.initiated === false && tx.address === address) {
return false;
}
if (filters.counterparty && tx.address !== filters.counterparty
&& tx.Destination !== filters.counterparty) {
return false;
}
return true;
}
function orderFilter(options, tx) {
return !options.startTx || (options.earliestFirst ?
utils.compareTransactions(tx, options.startTx) > 0 :
utils.compareTransactions(tx, options.startTx) < 0);
}
function getAccountTx(remote, address, options, marker, limit, callback) {
const params = {
account: address,
ledger_index_min: options.minLedgerVersion || -1,
ledger_index_max: options.maxLedgerVersion || -1,
forward: options.earliestFirst,
binary: options.binary,
limit: utils.clamp(limit, 10, 400),
marker: marker
};
remote.requestAccountTx(params, (error, data) => {
return error ? callback(error) : callback(null, {
marker: data.marker,
results: data.transactions
.filter((tx) => tx.validated)
.map(parseAccountTxTransaction)
.filter(_.partial(transactionFilter, address, options))
.filter(_.partial(orderFilter, options))
});
});
}
function checkForLedgerGaps(remote, options, transactions) {
let {minLedgerVersion, maxLedgerVersion} = options;
// if we reached the limit on number of transactions, then we can shrink
// the required ledger range to only guarantee that there are no gaps in
// the range of ledgers spanned by those transactions
if (options.limit && transactions.length === options.limit) {
if (options.earliestFirst) {
maxLedgerVersion = _.last(transactions).outcome.ledgerVersion;
} else {
minLedgerVersion = _.last(transactions).outcome.ledgerVersion;
}
}
if (!utils.hasCompleteLedgerRange(remote, minLedgerVersion,
maxLedgerVersion)) {
throw new utils.common.errors.MissingLedgerHistoryError();
}
}
function formatResponse(remote, options, transactions) {
const compare = options.earliestFirst ? utils.compareTransactions :
_.rearg(utils.compareTransactions, 1, 0);
const sortedTransactions = transactions.sort(compare);
checkForLedgerGaps(remote, options, sortedTransactions);
return sortedTransactions;
}
function getTransactionsInternal(remote, address, options, callback) {
const getter = _.partial(getAccountTx, remote, address, options);
const format = _.partial(formatResponse, remote, options);
utils.getRecursive(getter, options.limit, composeAsync(format, callback));
}
function getTransactions(address, options, callback) {
validate.address(address);
validate.getTransactionsOptions(options);
const defaults = {maxLedgerVersion: this.remote.getLedgerSequence()};
if (options.start) {
getTransaction.bind(this)(options.start, {}, (error, tx) => {
if (error) {
callback(error);
return;
}
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);
});
} else {
const newOptions = _.assign(defaults, options);
getTransactionsInternal(this.remote, address, newOptions, callback);
}
}
module.exports = utils.wrapCatch(getTransactions);

View File

@@ -0,0 +1,45 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const parseAccountTrustline = require('./parse/account-trustline');
function currencyFilter(currency, trustline) {
return currency === null || trustline.specification.currency === currency;
}
function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
callback) {
const requestOptions = {
account: address,
ledger: ledgerVersion,
marker: marker,
limit: utils.clamp(limit, 10, 400),
peer: options.counterparty
};
remote.requestAccountLines(requestOptions, (error, data) => {
return error ? callback(error) :
callback(null, {
marker: data.marker,
results: data.lines.map(parseAccountTrustline)
.filter(_.partial(currencyFilter, options.currency || null))
});
});
}
function getTrustlines(account: string, options: {currency: string,
counterparty: string, limit: number, ledgerVersion: number},
callback: () => void): void {
validate.address(account);
validate.getTrustlinesOptions(options);
const ledgerVersion = options.ledgerVersion
|| this.remote.getLedgerSequence();
const getter = _.partial(getAccountLines, this.remote, account,
ledgerVersion, options);
utils.getRecursive(getter, options.limit, callback);
}
module.exports = utils.wrapCatch(getTrustlines);

98
src/api/ledger/utils.js Normal file
View File

@@ -0,0 +1,98 @@
'use strict';
const _ = require('lodash');
const assert = require('assert');
const common = require('../common');
const dropsToXrp = common.dropsToXrp;
const composeAsync = common.composeAsync;
function clamp(value, min, max) {
assert(min <= max, 'Illegal clamp bounds');
return Math.min(Math.max(value, min), max);
}
function getXRPBalance(remote, address, ledgerVersion, callback) {
remote.requestAccountInfo({account: address, ledger: ledgerVersion},
composeAsync((data) => dropsToXrp(data.account_data.Balance), callback));
}
// 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) {
getter(marker, limit, (error, data) => {
if (error) {
return callback(error);
}
const remaining = limit - data.results.length;
if (remaining > 0 && data.marker !== undefined) {
getRecursiveRecur(getter, data.marker, remaining, (_error, results) => {
return _error ? callback(_error) :
callback(null, data.results.concat(results));
});
} else {
return callback(null, data.results.slice(0, limit));
}
});
}
function getRecursive(getter, limit, callback) {
getRecursiveRecur(getter, undefined, limit || Infinity, callback);
}
function renameCounterpartyToIssuer(amount) {
if (amount === undefined) {
return undefined;
}
const issuer = amount.counterparty === undefined ?
amount.issuer : amount.counterparty;
const withIssuer = _.assign({}, amount, {issuer: issuer});
return _.omit(withIssuer, 'counterparty');
}
function renameCounterpartyToIssuerInOrder(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};
return _.assign({}, order, _.omit(changes, _.isUndefined));
}
function signum(num) {
return (num === 0) ? 0 : (num > 0 ? 1 : -1);
}
/**
* Order two rippled transactions based on their ledger_index.
* If two transactions took place in the same ledger, sort
* them based on TransactionIndex
* See: https://ripple.com/build/transactions/
*
* @param {Object} first
* @param {Object} second
* @returns {Number} [-1, 0, 1]
*/
function compareTransactions(first, second) {
if (first.ledgerVersion === second.ledgerVersion) {
return signum(Number(first.indexInLedger) - Number(second.indexInLedger));
}
return Number(first.ledgerVersion) < Number(second.ledgerVersion) ? -1 : 1;
}
function hasCompleteLedgerRange(remote, minLedgerVersion, maxLedgerVersion) {
const firstLedgerVersion = 32570; // earlier versions have been lost
return remote.getServer().hasLedgerRange(
minLedgerVersion || firstLedgerVersion,
maxLedgerVersion || remote.getLedgerSequence());
}
module.exports = {
getXRPBalance,
compareTransactions,
renameCounterpartyToIssuer,
renameCounterpartyToIssuerInOrder,
getRecursive,
hasCompleteLedgerRange,
wrapCatch: common.wrapCatch,
clamp: clamp,
common: common
};

53
src/api/server/server.js Normal file
View File

@@ -0,0 +1,53 @@
/* @flow */
'use strict';
const common = require('../common');
// 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);
}
function disconnect(callback: (err: any, data: any) => void): void {
this.remote.disconnect(callback);
}
function isUpToDate(remote): boolean {
const server = remote.getServer();
return Boolean(server) && (remote._stand_alone
|| (Date.now() - server._lastLedgerClose) <= CONNECTION_TIMEOUT);
}
function isConnected(): boolean {
return Boolean(this.remote._ledger_current_index) && isUpToDate(this.remote);
}
function getServerInfo(callback: (err: any, data: any) => void): void {
this.remote.requestServerInfo((error, response) => {
if (error) {
callback(new common.errors.RippledNetworkError(error.message));
} else {
callback(null, response.info);
}
});
}
function getFee(): number {
return common.dropsToXrp(this.remote.createTransaction()._computeFee());
}
function getLedgerVersion(): number {
return this.remote.getLedgerSequence();
}
module.exports = {
connect,
disconnect,
isConnected,
getServerInfo,
getFee,
getLedgerVersion
};

View File

@@ -0,0 +1,38 @@
/* @flow */
'use strict';
const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
const OfferCreateFlags = {
passive: {set: 'Passive'},
immediateOrCancel: {set: 'ImmediateOrCancel'},
fillOrKill: {set: 'FillOrKill'}
};
function createOrderTransaction(account, order) {
validate.address(account);
validate.order(order);
const transaction = new Transaction();
const takerPays = utils.common.toRippledAmount(order.direction === 'buy' ?
order.quantity : order.totalPrice);
const takerGets = utils.common.toRippledAmount(order.direction === 'buy' ?
order.totalPrice : order.quantity);
transaction.offerCreate(account, takerPays, takerGets);
utils.setTransactionBitFlags(transaction, order, OfferCreateFlags);
if (order.direction === 'sell') {
transaction.setFlags('Sell');
}
return transaction;
}
function prepareOrder(account, order, instructions, callback) {
const transaction = createOrderTransaction(account, order);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(prepareOrder);

View File

@@ -0,0 +1,21 @@
/* @flow */
'use strict';
const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
function createOrderCancellationTransaction(account, sequence) {
validate.address(account);
validate.sequence(sequence);
const transaction = new Transaction();
transaction.offerCancel(account, sequence);
return transaction;
}
function prepareOrderCancellation(account, sequence, instructions, callback) {
const transaction = createOrderCancellationTransaction(account, sequence);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(prepareOrderCancellation);

View File

@@ -0,0 +1,93 @@
/* @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 isIOUWithoutCounterparty(amount) {
return amount && amount.currency !== 'XRP'
&& amount.counterparty === undefined;
}
function applyAnyCounterpartyEncoding(payment) {
// Convert blank counterparty to sender or receiver's address
// (Ripple convention for 'any counterparty')
// 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.destination.amount)) {
payment.destination.amount.counterparty = payment.destination.address;
}
}
function createPaymentTransaction(account, payment) {
applyAnyCounterpartyEncoding(payment);
validate.address(account);
validate.payment(payment);
const transaction = new Transaction();
transaction.payment({
from: payment.source.address,
to: payment.destination.address,
amount: toRippledAmount(payment.destination.amount)
});
if (payment.invoiceID) {
transaction.invoiceID(payment.invoiceID);
}
if (payment.source.tag) {
transaction.sourceTag(payment.source.tag);
}
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)
);
}
if (payment.allowPartialPayment) {
transaction.setFlags(['PartialPayment']);
}
if (payment.noDirectRipple) {
transaction.setFlags(['NoRippleDirect']);
}
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));
}
return transaction;
}
function preparePayment(account, payment, instructions, callback) {
const transaction = createPaymentTransaction(account, payment);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(preparePayment);

View File

@@ -0,0 +1,98 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const utils = require('./utils');
const validate = utils.common.validate;
const AccountFlagIndices = utils.common.constants.AccountFlagIndices;
const AccountFields = utils.common.constants.AccountFields;
const Transaction = utils.common.core.Transaction;
// Emptry string passed to setting will clear it
const CLEAR_SETTING = '';
function setTransactionFlags(transaction, values) {
const keys = Object.keys(values);
assert(keys.length === 1, 'ERROR: can only set one setting per transaction');
const flagName = keys[0];
const value = values[flagName];
const index = AccountFlagIndices[flagName];
if (index !== undefined) {
if (value) {
transaction.tx_json.SetFlag = index;
} else {
transaction.tx_json.ClearFlag = index;
}
}
}
function setTransactionFields(transaction, input) {
const fieldSchema = AccountFields;
for (const fieldName in fieldSchema) {
const field = fieldSchema[fieldName];
let value = input[field.name];
if (value === undefined) {
continue;
}
// The value required to clear an account root field varies
if (value === CLEAR_SETTING && field.hasOwnProperty('defaults')) {
value = field.defaults;
}
if (field.encoding === 'hex' && !field.length) {
// This is currently only used for Domain field
value = new Buffer(value, 'ascii').toString('hex').toUpperCase();
}
transaction.tx_json[fieldName] = value;
}
}
/**
* Note: A fee of 1% requires 101% of the destination to be sent for the
* destination to receive 100%.
* The transfer rate is specified as the input amount as fraction of 1.
* To specify the default rate of 0%, a 100% input amount, specify 1.
* To specify a rate of 1%, a 101% input amount, specify 1.01
*
* @param {Number|String} transferRate
*
* @returns {Number|String} numbers will be converted while strings
* are returned
*/
function convertTransferRate(transferRate) {
return _.isNumber(transferRate) ? transferRate * 1e9 : transferRate;
}
function createSettingsTransaction(account, settings) {
validate.address(account);
validate.settings(settings);
const transaction = new Transaction();
if (settings.regularKey) {
return transaction.setRegularKey({
account: account,
regular_key: settings.regularKey
});
}
transaction.accountSet(account);
setTransactionFlags(transaction, settings);
setTransactionFields(transaction, settings);
if (transaction.tx_json.TransferRate !== undefined) {
transaction.tx_json.TransferRate = convertTransferRate(
transaction.tx_json.TransferRate);
}
return transaction;
}
function prepareSettings(account, settings, instructions, callback) {
const transaction = createSettingsTransaction(account, settings);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(prepareSettings);

View File

@@ -0,0 +1,68 @@
/* @flow */
'use strict';
const utils = require('./utils');
const core = utils.common.core;
const validate = utils.common.validate;
/**
* These prefixes are inserted before the source material used to
* generate various hashes. This is done to put each hash in its own
* "space." This way, two different types of objects with the
* same binary data will produce different hashes.
*
* Each prefix is a 4-byte value with the last byte set to zero
* and the first three bytes formed from the ASCII equivalent of
* 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 getPublicKeyHex(keypair) {
return keypair.to_hex_pub();
}
function serialize(txJSON) {
return core.SerializedObject.from_json(txJSON);
}
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 computeSignature(txJSON, keypair) {
const signature = keypair.sign(signingHash(txJSON));
return core.sjcl.codec.hex.fromBits(signature).toUpperCase();
}
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});
const keypair = getKeyPair(txJSON.Account, secret);
if (txJSON.SigningPubKey === undefined) {
txJSON.SigningPubKey = getPublicKeyHex(keypair);
}
txJSON.TxnSignature = computeSignature(txJSON, keypair);
const serialized = serialize(txJSON);
return {
signedTransaction: serialized.to_hex(),
id: hashSerialization(serialized, HASH_TX_ID)
};
}
module.exports = sign;

View File

@@ -0,0 +1,15 @@
/* @flow */
'use strict';
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);
const request = new Request(this.remote, 'submit');
request.message.tx_blob = tx_blob;
request.request(null, callback);
}
module.exports = submit;

View File

@@ -0,0 +1,35 @@
/* @flow */
'use strict';
const utils = require('./utils');
const validate = utils.common.validate;
const Transaction = utils.common.core.Transaction;
const TrustSetFlags = {
authorized: {set: 'SetAuth'},
allowRippling: {set: 'ClearNoRipple', unset: 'NoRipple'},
frozen: {set: 'SetFreeze', unset: 'ClearFreeze'}
};
function createTrustlineTransaction(account, trustline) {
validate.address(account);
validate.trustline(trustline);
const limit = {
currency: trustline.currency,
issuer: trustline.counterparty,
value: trustline.limit
};
const transaction = new Transaction();
transaction.trustSet(account, limit,
trustline.qualityIn, trustline.qualityOut);
utils.setTransactionBitFlags(transaction, trustline, TrustSetFlags);
return transaction;
}
function prepareTrustline(account, trustline, instructions, callback) {
const transaction = createTrustlineTransaction(account, trustline);
utils.createTxJSON(transaction, this.remote, instructions, callback);
}
module.exports = utils.wrapCatch(prepareTrustline);

View File

@@ -0,0 +1,70 @@
/* @flow */
'use strict';
const BigNumber = require('bignumber.js');
const common = require('../common');
function setTransactionBitFlags(transaction: any, values: any, flags: any):
void {
for (const flagName in flags) {
const flagValue = values[flagName];
const flagConversions = flags[flagName];
if (flagValue === true && flagConversions.set !== undefined) {
transaction.setFlags(flagConversions.set);
}
if (flagValue === false && flagConversions.unset !== undefined) {
transaction.setFlags(flagConversions.unset);
}
}
}
function getFeeDrops(remote) {
const feeUnits = 10; // all transactions currently have a fee of 10 fee units
return remote.feeTx(feeUnits).to_text();
}
function createTxJSON(transaction: any, remote: any, instructions: any,
callback: (err: ?(typeof Error), data: {tx_json: any}) => void): void {
common.validate.instructions(instructions);
transaction.complete();
const account = transaction.getAccount();
const txJSON = transaction.tx_json;
if (instructions.maxLedgerVersion !== undefined) {
txJSON.LastLedgerSequence = parseInt(instructions.maxLedgerVersion, 10);
} else {
const offset = instructions.maxLedgerVersionOffset !== undefined ?
parseInt(instructions.maxLedgerVersionOffset, 10) : 3;
txJSON.LastLedgerSequence = remote.getLedgerSequence() + offset;
}
if (instructions.fee !== undefined) {
txJSON.Fee = common.xrpToDrops(instructions.fee);
} else {
const serverFeeDrops = getFeeDrops(remote);
if (instructions.maxFee !== undefined) {
const maxFeeDrops = common.xrpToDrops(instructions.maxFee);
txJSON.Fee = BigNumber.min(serverFeeDrops, maxFeeDrops).toString();
} else {
txJSON.Fee = serverFeeDrops;
}
}
if (instructions.sequence !== undefined) {
txJSON.Sequence = parseInt(instructions.sequence, 10);
callback(null, txJSON);
} else {
remote.findAccount(account).getNextSequence(function(error, sequence) {
txJSON.Sequence = sequence;
callback(null, txJSON);
});
}
}
module.exports = {
setTransactionBitFlags: setTransactionBitFlags,
createTxJSON: createTxJSON,
wrapCatch: common.wrapCatch,
common: common
};

View File

@@ -1,3 +1,4 @@
'use strict';
// Routines for working with an account.
//
// You should not instantiate this class yourself, instead use Remote#account.
@@ -9,16 +10,15 @@
// balance_proposed
//
// var network = require('./network.js');
var async = require('async');
var util = require('util');
var extend = require('extend');
var EventEmitter = require('events').EventEmitter;
var Amount = require('./amount').Amount;
var UInt160 = require('./uint160').UInt160;
var TransactionManager = require('./transactionmanager').TransactionManager;
var sjcl = require('./utils').sjcl;
var Base = require('./base').Base;
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;
/**
* @constructor Account
@@ -29,7 +29,7 @@ var Base = require('./base').Base;
function Account(remote, account) {
EventEmitter.call(this);
var self = this;
const self = this;
this._remote = remote;
this._account = UInt160.from_json(account);
@@ -40,29 +40,29 @@ function Account(remote, account) {
// Important: This must never be overwritten, only extend()-ed
this._entry = { };
function listenerAdded(type, listener) {
function listenerAdded(type) {
if (~Account.subscribeEvents.indexOf(type)) {
if (!self._subs && self._remote._connected) {
self._remote.request_subscribe()
.add_account(self._account_id)
self._remote.requestSubscribe()
.addAccount(self._account_id)
.broadcast().request();
}
self._subs += 1;
}
};
}
this.on('newListener', listenerAdded);
function listenerRemoved(type, listener) {
function listenerRemoved(type) {
if (~Account.subscribeEvents.indexOf(type)) {
self._subs -= 1;
if (!self._subs && self._remote._connected) {
self._remote.request_unsubscribe()
.add_account(self._account_id)
self._remote.requestUnsubscribe()
.addAccount(self._account_id)
.broadcast().request();
}
}
};
}
this.on('removeListener', listenerRemoved);
@@ -70,7 +70,7 @@ function Account(remote, account) {
if (self._account.is_valid() && self._subs) {
request.add_account(self._account_id);
}
};
}
this._remote.on('prepare_subscribe', attachAccount);
@@ -79,11 +79,11 @@ function Account(remote, account) {
return;
}
var changed = false;
let changed = false;
transaction.mmeta.each(function(an) {
var isAccount = an.fields.Account === self._account_id;
var isAccountRoot = isAccount && (an.entryType === 'AccountRoot');
const isAccount = an.fields.Account === self._account_id;
const isAccountRoot = isAccount && (an.entryType === 'AccountRoot');
if (isAccountRoot) {
extend(self._entry, an.fieldsNew, an.fieldsFinal);
@@ -94,14 +94,14 @@ function Account(remote, account) {
if (changed) {
self.emit('entry', self._entry);
}
};
}
this.on('transaction', handleTransaction);
this._transactionManager = new TransactionManager(this);
return this;
};
}
util.inherits(Account, EventEmitter);
@@ -109,7 +109,7 @@ util.inherits(Account, EventEmitter);
* List of events that require a remote subscription to the account.
*/
Account.subscribeEvents = [ 'transaction', 'entry' ];
Account.subscribeEvents = ['transaction', 'entry'];
Account.prototype.toJson = function() {
return this._account.to_json();
@@ -144,9 +144,9 @@ Account.prototype.getInfo = function(callback) {
* @param {Function} callback
*/
Account.prototype.entry = function(callback) {
var self = this;
var callback = typeof callback === 'function' ? callback : function(){};
Account.prototype.entry = function(callback_) {
const self = this;
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
function accountInfo(err, info) {
if (err) {
@@ -156,21 +156,21 @@ Account.prototype.entry = function(callback) {
self.emit('entry', self._entry);
callback(null, info);
}
};
}
this.getInfo(accountInfo);
return this;
};
Account.prototype.getNextSequence = function(callback) {
var callback = typeof callback === 'function' ? callback : function(){};
Account.prototype.getNextSequence = function(callback_) {
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
function isNotFound(err) {
return err && typeof err === 'object'
&& typeof err.remote === 'object'
&& err.remote.error === 'actNotFound';
};
}
function accountInfo(err, info) {
if (isNotFound(err)) {
@@ -181,7 +181,7 @@ Account.prototype.getNextSequence = function(callback) {
} else {
callback(null, info.account_data.Sequence);
}
};
}
this.getInfo(accountInfo);
@@ -197,9 +197,9 @@ Account.prototype.getNextSequence = function(callback) {
* @param {function(err, lines)} callback Called with the result
*/
Account.prototype.lines = function(callback) {
var self = this;
var callback = typeof callback === 'function' ? callback : function(){};
Account.prototype.lines = function(callback_) {
const self = this;
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
function accountLines(err, res) {
if (err) {
@@ -225,23 +225,22 @@ Account.prototype.lines = function(callback) {
* @returns {Account}
*/
Account.prototype.line = function(currency, address, callback) {
var self = this;
var callback = typeof callback === 'function' ? callback : function(){};
Account.prototype.line = function(currency, address, callback_) {
const self = this;
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
self.lines(function(err, data) {
if (err) {
return callback(err);
}
var line;
let line;
top:
for (var i=0; i<data.lines.length; i++) {
var l = data.lines[i];
for (let i = 0; i < data.lines.length; i++) {
const l = data.lines[i];
if (l.account === address && l.currency === currency) {
line = l;
break top;
break;
}
}
@@ -271,15 +270,16 @@ Account.prototype.notifyTx = function(transaction) {
this.emit('transaction', transaction);
var account = transaction.transaction.Account;
const account = transaction.transaction.Account;
if (!account) {
return;
}
var isThisAccount = (account === this._account_id);
const isThisAccount = (account === this._account_id);
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound', transaction);
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound',
transaction);
};
/**
@@ -297,16 +297,17 @@ Account.prototype.submit = function(transaction) {
/**
* Check whether the given public key is valid for this account
*
* @param {Hex-encoded String|RippleAddress} public_key
* @param {Function} callback
* @param {Hex-encoded_String|RippleAddress} public_key Public key
* @param {Function} callback Is a callback
* @returns {void}
*
* @callback
* @param {Error} err
* @param {Boolean} true if the public key is valid and active, false otherwise
* param {Error} err
* param {Boolean} true if the public key is valid and active, false otherwise
*/
Account.prototype.publicKeyIsActive = function(public_key, callback) {
var self = this;
var public_key_as_uint160;
const self = this;
let public_key_as_uint160;
try {
public_key_as_uint160 = Account._publicKeyToAddress(public_key);
@@ -315,7 +316,7 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
}
function getAccountInfo(async_callback) {
self.getInfo(function(err, account_info_res){
self.getInfo(function(err, account_info_res) {
// If the remote responds with an Account Not Found error then the account
// is unfunded and thus we can assume that the master key is active
@@ -325,7 +326,7 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
async_callback(err, account_info_res);
}
});
};
}
function publicKeyIsValid(account_info_res, async_callback) {
// Catch the case of unfunded accounts
@@ -340,10 +341,11 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
return;
}
var account_info = account_info_res.account_data;
const account_info = account_info_res.account_data;
// Respond with true if the RegularKey is set and matches the given public key or
// if the public key matches the account address and the lsfDisableMaster is not set
// Respond with true if the RegularKey is set and matches the given
// public key or if the public key matches the account address and
// the lsfDisableMaster is not set
if (account_info.RegularKey &&
account_info.RegularKey === public_key_as_uint160) {
async_callback(null, true);
@@ -353,9 +355,9 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
} else {
async_callback(null, false);
}
};
}
var steps = [
const steps = [
getAccountInfo,
publicKeyIsValid
];
@@ -368,25 +370,25 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
*
* @static
*
* @param {Hex-encoded string|RippleAddress} public_key
* @returns {RippleAddress}
* @param {Hex-encoded_string|RippleAddress} public_key Public key
* @returns {RippleAddress} Ripple Address
*/
Account._publicKeyToAddress = function(public_key) {
// Based on functions in /src/js/ripple/keypair.js
function hexToUInt160(public_key) {
var bits = sjcl.codec.hex.toBits(public_key);
var hash = sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
var address = UInt160.from_bits(hash);
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();
};
}
if (UInt160.is_valid(public_key)) {
return public_key;
} else if (/^[0-9a-fA-F]+$/.test(public_key)) {
return hexToUInt160(public_key);
} else {
} else { // eslint-disable-line no-else-return
throw new Error('Public key is invalid. Must be a UInt160 or a hex string');
}
};

View File

@@ -1,71 +1,63 @@
'use strict';
// Represent Ripple amounts and currencies.
// - Numbers in hex are big-endian.
var assert = require('assert');
var extend = require('extend');
var utils = require('./utils');
var UInt160 = require('./uint160').UInt160;
var Seed = require('./seed').Seed;
var Currency = require('./currency').Currency;
var BigNumber = require('./bignumber');
const assert = require('assert');
const extend = require('extend');
const utils = require('./utils');
const UInt160 = require('./uint160').UInt160;
const Seed = require('./seed').Seed;
const Currency = require('./currency').Currency;
const Value = require('./value').Value;
const IOUValue = require('./iouvalue').IOUValue;
const XRPValue = require('./xrpvalue').XRPValue;
function isInteger(number) {
return parseInt(number) === number;
}
function ensureDecimalPoint(value) {
return isInteger(value) ? String(value) + '.0' : value;
}
function inverse(number) {
return (new BigNumber(number)).toPower(-1);
}
function Amount() {
function Amount(value = new XRPValue(NaN)) {
// Json format:
// integer : XRP
// { 'value' : ..., 'currency' : ..., 'issuer' : ...}
assert(value instanceof Value);
this._value = new BigNumber(NaN);
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
this._currency = new Currency();
this._issuer = new UInt160();
this._value = value;
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
this._currency = new Currency();
this._issuer = new UInt160();
}
var consts = {
currency_xns: 0,
currency_one: 1,
xns_precision: 6,
/**
* Set strict_mode = false to disable amount range checking
*/
Amount.strict_mode = true;
const consts = {
currency_xns: 0,
currency_one: 1,
xns_precision: 6,
// bi_ prefix refers to "big integer"
// TODO: we shouldn't expose our BigNumber library publicly
bi_5: new BigNumber(5),
bi_7: new BigNumber(7),
bi_10: new BigNumber(10),
bi_1e14: new BigNumber(1e14),
bi_1e16: new BigNumber(1e16),
bi_1e17: new BigNumber(1e17),
bi_1e32: new BigNumber(1e32),
bi_man_max_value: new BigNumber('9999999999999999'),
bi_man_min_value: new BigNumber(1e15),
bi_xns_max: new BigNumber(1e17),
bi_xns_min: new BigNumber(-1e17),
bi_xns_unit: new BigNumber(1e6),
// man refers to mantissa
bi_man_max_value: '9999999999999999',
bi_man_min_value: Number(1e15).toString(),
bi_xns_max: Number(1e17).toString(),
bi_xns_min: Number(-1e17).toString(),
cMinOffset: -96,
cMaxOffset: 80,
cMinOffset: -96,
cMaxOffset: 80,
// Maximum possible amount for non-XRP currencies using the maximum mantissa
// with maximum exponent. Corresponds to hex 0xEC6386F26FC0FFFF.
max_value: '9999999999999999e80',
max_value: '9999999999999999e80',
// Minimum possible amount for non-XRP currencies.
min_value: '-1000000000000000e-96'
min_value: '-1000000000000000e-96'
};
var MAX_XRP_VALUE = new BigNumber(1e11);
var MAX_IOU_VALUE = new BigNumber(consts.max_value);
var MIN_IOU_VALUE = (new BigNumber(consts.min_value)).abs();
const MAX_XRP_VALUE = new XRPValue(1e11);
const MAX_IOU_VALUE = new IOUValue(consts.max_value);
const MIN_IOU_VALUE = new IOUValue(consts.min_value).abs();
const bi_xns_unit = new IOUValue(1e6);
// Add constants to Amount class
extend(Amount, consts);
@@ -108,32 +100,36 @@ Amount.is_valid_full = function(j) {
};
Amount.NaN = function() {
var result = new Amount();
result._value = new BigNumber(NaN); // should have no effect
const result = new Amount();
result._value = new IOUValue(NaN); // should have no effect
return result; // but let's be careful
};
// be sure that _is_native is set properly BEFORE calling _set_value
Amount.prototype._set_value = function(value, roundingMode) {
assert(value instanceof BigNumber);
this._value = value.isZero() && value.isNegative() ? value.negated() : value;
this.canonicalize(roundingMode);
Amount.prototype._set_value = function(value: Value) {
this._value = value.isZero() && value.isNegative() ?
value.negate() : value;
this._check_limits();
};
// Returns a new value which is the absolute value of this.
Amount.prototype.abs = function() {
return this.clone(this.is_negative());
return this._copy(this._value.abs());
};
Amount.prototype.add = function(addend) {
var addendAmount = Amount.from_json(addend);
const addendAmount = Amount.from_json(addend);
if (!this.is_comparable(addendAmount)) {
return Amount.NaN();
return new Amount();
}
return this._copy(this._value.plus(addendAmount._value));
return this._copy(this._value.add(addendAmount._value));
};
Amount.prototype.subtract = function(subtrahend) {
@@ -143,30 +139,21 @@ Amount.prototype.subtract = function(subtrahend) {
// XXX Diverges from cpp.
Amount.prototype.multiply = function(multiplicand) {
var multiplicandAmount = Amount.from_json(multiplicand);
// TODO: probably should just multiply by multiplicandAmount._value
var multiplyBy = multiplicandAmount.is_native() ?
multiplicandAmount._value.times(Amount.bi_xns_unit)
: multiplicandAmount._value;
return this._copy(this._value.times(multiplyBy));
const multiplicandAmount = Amount.from_json(multiplicand);
return this._copy(this._value.multiply(multiplicandAmount._value));
};
Amount.prototype.scale = function(scaleFactor) {
return this.multiply(scaleFactor);
};
Amount.prototype.divide = function(divisor) {
var divisorAmount = Amount.from_json(divisor);
if (!this.is_valid()) {
throw new Error('Invalid dividend');
}
if (!divisorAmount.is_valid()) {
throw new Error('Invalid divisor');
}
if (divisorAmount.is_zero()) {
throw new Error('divide by zero');
}
// TODO: probably should just divide by divisorAmount._value
var divideBy = divisorAmount.is_native() ?
divisorAmount._value.times(Amount.bi_xns_unit)
: divisorAmount._value;
return this._copy(this._value.dividedBy(divideBy));
const divisorAmount = Amount.from_json(divisor);
return this._copy(this._value.divide(divisorAmount._value));
};
/**
@@ -178,37 +165,39 @@ Amount.prototype.divide = function(divisor) {
* price would be rendered as USD.
*
* @example
* var price = buy_amount.ratio_human(sell_amount);
* const price = buy_amount.ratio_human(sell_amount);
*
* @this {Amount} The numerator (top half) of the fraction.
* @param {Amount} denominator The denominator (bottom half) of the fraction.
* @param opts Options for the calculation.
* @param opts.reference_date {Date|Number} Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @param opts.reference_date {Date|Number} Date based on which
* demurrage/interest should be applied. Can be given as JavaScript Date or int
* for Ripple epoch.
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
*/
Amount.prototype.ratio_human = function(denominator, opts) {
opts = extend({ }, opts);
Amount.prototype.ratio_human = function(denom, opts) {
const options = extend({ }, opts);
var numerator = this;
denominator = Amount.from_json(ensureDecimalPoint(denominator));
const numerator = this.clone();
let denominator = Amount.from_json(denom);
// If either operand is NaN, the result is NaN.
if (!numerator.is_valid() || !denominator.is_valid()) {
return Amount.NaN();
return new Amount(NaN);
}
if (denominator.is_zero()) {
return Amount.NaN();
return new Amount(NaN);
}
// Apply interest/demurrage
//
// We only need to apply it to the second factor, because the currency unit of
// the first factor will carry over into the result.
if (opts.reference_date) {
denominator = denominator.applyInterest(opts.reference_date);
if (options.reference_date) {
denominator = denominator.applyInterest(options.reference_date);
}
// Special case: The denominator is a native (XRP) amount.
@@ -222,7 +211,7 @@ Amount.prototype.ratio_human = function(denominator, opts) {
//
// To compensate, we multiply the numerator by 10^xns_precision.
if (denominator._is_native) {
numerator._set_value(numerator._value.times(Amount.bi_xns_unit));
numerator._set_value(numerator.multiply(bi_xns_unit));
}
return numerator.divide(denominator);
@@ -238,43 +227,43 @@ Amount.prototype.ratio_human = function(denominator, opts) {
* Intended use is to calculate something like: 10 USD * 10 XRP/USD = 100 XRP
*
* @example
* var sell_amount = buy_amount.product_human(price);
* let sell_amount = buy_amount.product_human(price);
*
* @see Amount#ratio_human
*
* @this {Amount} The first factor of the product.
* @param {Amount} factor The second factor of the product.
* @param opts Options for the calculation.
* @param opts.reference_date {Date|Number} Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @param {Object} opts Options for the calculation.
* @param {Date|Number} opts.reference_date Date based on which
* demurrage/interest should be applied. Can be given as JavaScript Date or int
* for Ripple epoch.
* @return {Amount} The product. Unit will be the same as the first factor.
*/
Amount.prototype.product_human = function(factor, opts) {
opts = opts || {};
Amount.prototype.product_human = function(factor, options = {}) {
factor = Amount.from_json(ensureDecimalPoint(factor));
let fac = Amount.from_json(factor);
// If either operand is NaN, the result is NaN.
if (!this.is_valid() || !factor.is_valid()) {
return Amount.NaN();
if (!this.is_valid() || !fac.is_valid()) {
return new Amount();
}
// Apply interest/demurrage
//
// We only need to apply it to the second factor, because the currency unit of
// the first factor will carry over into the result.
if (opts.reference_date) {
factor = factor.applyInterest(opts.reference_date);
if (options.reference_date) {
fac = fac.applyInterest(options.reference_date);
}
var product = this.multiply(factor);
const product = this.multiply(fac);
// Special case: The second factor is a native (XRP) amount expressed as base
// units (1 XRP = 10^xns_precision base units).
//
// See also Amount#ratio_human.
if (factor._is_native) {
product._set_value(product._value.dividedBy(Amount.bi_xns_unit));
if (fac._is_native) {
const quotient = product.divide(bi_xns_unit.toString());
product._set_value(quotient._value);
}
return product;
@@ -283,10 +272,11 @@ Amount.prototype.product_human = function(factor, opts) {
/**
* Turn this amount into its inverse.
*
* @return {Amount} self
* @private
*/
Amount.prototype._invert = function() {
this._set_value(inverse(this._value));
this._set_value(this._value.invert());
return this;
};
@@ -301,10 +291,11 @@ Amount.prototype.invert = function() {
};
/**
* Canonicalize amount value
* Canonicalize amount value is now taken care of in the Value classes
*
* Mirrors rippled's internal Amount representation
* From https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol/STAmount.h#L31-L40
* From https://github.com/ripple/rippled/blob/develop/src/ripple/data
* /protocol/STAmount.h#L31-L40
*
* Internal form:
* 1: If amount is zero, then value is zero and offset is -100
@@ -325,29 +316,18 @@ Amount.prototype.invert = function() {
* - maximum: (10^16)-1 x 10^80 -> 9999999999999999e80
*
* @returns {Amount}
* @throws {Error} if offset exceeds legal ranges, meaning the amount value is bigger than supported
* @throws {Error} if offset exceeds legal ranges, meaning the amount value is
* bigger than supported
*/
Amount.prototype.canonicalize = function(roundingMode) {
if (this._is_native) {
this._value = this._value.round(6, BigNumber.ROUND_DOWN);
} else {
if (roundingMode) {
var value = this._value;
this._value = BigNumber.withRoundingMode(roundingMode, function() {
return new BigNumber(value.toPrecision(16));
});
} else {
this._value = new BigNumber(this._value.toPrecision(16));
}
}
};
Amount.prototype._check_limits = function() {
if (!Amount.strict_mode) {
return this;
}
if (this._value.isNaN() || this._value.isZero()) {
return this;
}
var absval = this._value.absoluteValue();
const absval = this._value.abs();
if (this._is_native) {
if (absval.greaterThan(MAX_XRP_VALUE)) {
throw new Error('Exceeding max value of ' + MAX_XRP_VALUE.toString());
@@ -368,15 +348,15 @@ Amount.prototype.clone = function(negate) {
};
Amount.prototype._copy = function(value) {
var copy = this.clone();
const copy = this.clone();
copy._set_value(value);
return copy;
};
Amount.prototype.compareTo = function(to) {
var toAmount = Amount.from_json(to);
const toAmount = Amount.from_json(to);
if (!this.is_comparable(toAmount)) {
return Amount.NaN();
return new Amount();
}
return this._value.comparedTo(toAmount._value);
};
@@ -384,10 +364,10 @@ Amount.prototype.compareTo = function(to) {
// Make d a copy of this. Returns d.
// Modification of objects internally refered to is not allowed.
Amount.prototype.copyTo = function(d, negate) {
d._value = negate ? this._value.negated() : this._value;
d._value = negate ? this._value.negate() : this._value;
d._is_native = this._is_native;
d._currency = this._currency;
d._issuer = this._issuer;
d._currency = this._currency;
d._issuer = this._issuer;
return d;
};
@@ -430,7 +410,9 @@ Amount.prototype.is_valid = function() {
};
Amount.prototype.is_valid_full = function() {
return this.is_valid() && this._currency.is_valid() && this._issuer.is_valid();
return this.is_valid()
&& this._currency.is_valid()
&& this._issuer.is_valid();
};
Amount.prototype.is_zero = function() {
@@ -457,23 +439,29 @@ Amount.prototype.negate = function() {
* 100 => 100000000/XRP
*
*
* The regular expression below matches above cases, broken down for better understanding:
* The regular expression below matches above cases, broken down for better
* understanding:
*
* ([A-z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217
* $ // end of string
* // either 3 letter alphabetic currency-code or 3 digit numeric currency-code.
* // See ISO 4217
* ([A-z]{3}|[0-9]{3})
*
* // end of string
* $
*/
Amount.prototype.parse_human = function(j, opts) {
opts = opts || {};
Amount.prototype.parse_human = function(j, options) {
const opts = options || {};
var hex_RE = /^[a-fA-F0-9]{40}$/;
var currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/;
const hex_RE = /^[a-fA-F0-9]{40}$/;
const currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/;
var value;
var currency;
let value;
let currency;
var words = j.split(' ').filter(function(word) { return word !== ''; });
const words = j.split(' ').filter(function(word) {
return word !== '';
});
function isNumber(s) {
return isFinite(s) && s !== '' && s !== null;
@@ -487,7 +475,7 @@ Amount.prototype.parse_human = function(j, opts) {
value = words[0].slice(0, -3);
currency = words[0].slice(-3);
if (!(isNumber(value) && currency.match(currency_RE))) {
return Amount.NaN();
return new Amount();
}
}
} else if (words.length === 2) {
@@ -501,28 +489,32 @@ Amount.prototype.parse_human = function(j, opts) {
value = words[0];
currency = words[1];
} else {
return Amount.NaN();
return new Amount();
}
} else {
return Amount.NaN();
return new Amount();
}
currency = currency.toUpperCase();
this.set_currency(currency);
this._is_native = (currency === 'XRP');
this._set_value(new BigNumber(value));
const newValue =
(this._is_native ? new XRPValue(value) :
new IOUValue(value));
this._set_value(newValue);
// Apply interest/demurrage
if (opts.reference_date && this._currency.has_interest()) {
var interest = this._currency.get_interest_at(opts.reference_date);
this._set_value(this._value.dividedBy(interest.toString()));
const interest = this._currency.get_interest_at(opts.reference_date);
this._set_value(
this._value.divide(new IOUValue(interest.toString())));
}
return this;
};
Amount.prototype.parse_issuer = function(issuer) {
this._issuer = UInt160.from_json(issuer);
this._issuer = UInt160.from_json(issuer);
return this;
};
@@ -539,41 +531,42 @@ Amount.prototype.parse_issuer = function(issuer) {
* Prices involving demurraging currencies are tricky, since they depend on the
* base and counter currencies.
*
* @param quality {String} 8 hex bytes quality or 32 hex bytes BookDirectory
* @param {String} quality 8 hex bytes quality or 32 hex bytes BookDirectory
* index.
* @param counterCurrency {Currency|String} Currency of the resulting Amount
* @param {Currency|String} counterCurrency currency of the resulting Amount
* object.
* @param counterIssuer {Issuer|String} Issuer of the resulting Amount object.
* @param opts Additional options
* @param opts.inverse {Boolean} If true, return the inverse of the price
* @param {Issuer|String} counterIssuer Issuer of the resulting Amount object.
* @param {Object} opts Additional options
* @param {Boolean} opts.inverse If true, return the inverse of the price
* encoded in the quality.
* @param opts.base_currency {Currency|String} The other currency. This plays a
* @param {Currency|String} opts.base_currency The other currency. This plays a
* role with interest-bearing or demurrage currencies. In that case the
* demurrage has to be applied when the quality is decoded, otherwise the
* price will be false.
* @param opts.reference_date {Date|Number} Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @param opts.xrp_as_drops {Boolean} Whether XRP amount should be treated as
* @param {Date|Number} opts.reference_date Date based on which
* demurrage/interest should be applied. Can be given as JavaScript Date or int
* for Ripple epoch.
* @param {Boolean} opts.xrp_as_drops Whether XRP amount should be treated as
* drops. When the base currency is XRP, the quality is calculated in drops.
* For human use however, we want to think of 1000000 drops as 1 XRP and
* prices as per-XRP instead of per-drop.
* @return {Amount} self
*/
Amount.prototype.parse_quality = function(quality, counterCurrency, counterIssuer, opts)
{
opts = opts || {};
Amount.prototype.parse_quality =
function(quality, counterCurrency, counterIssuer, opts) {
const options = opts || {};
var baseCurrency = Currency.from_json(opts.base_currency);
const baseCurrency = Currency.from_json(options.base_currency);
var mantissa_hex = quality.substring(quality.length-14);
var offset_hex = quality.substring(quality.length-16, quality.length-14);
var mantissa = new BigNumber(mantissa_hex, 16);
var offset = parseInt(offset_hex, 16) - 100;
const mantissa_hex = quality.substring(quality.length - 14);
const offset_hex = quality.substring(
quality.length - 16, quality.length - 14);
const mantissa = new IOUValue(mantissa_hex, null, 16);
const offset = parseInt(offset_hex, 16) - 100;
var value = new BigNumber(mantissa.toString() + 'e' + offset.toString());
this._currency = Currency.from_json(counterCurrency);
this._issuer = UInt160.from_json(counterIssuer);
this._is_native = this._currency.is_native();
this._currency = Currency.from_json(counterCurrency);
this._issuer = UInt160.from_json(counterIssuer);
this._is_native = this._currency.is_native();
if (this._is_native && baseCurrency.is_native()) {
throw new Error('XRP/XRP quality is not allowed');
@@ -592,10 +585,11 @@ Amount.prototype.parse_quality = function(quality, counterCurrency, counterIssue
quality as stored : 5 USD / 3000000 drops
inverted : 3000000 drops / 5 USD
*/
var adjusted = opts.inverse ? inverse(value) : value;
var nativeAdjusted = adjusted;
const valueStr = mantissa.toString() + 'e' + offset.toString();
let nativeAdjusted = new IOUValue(valueStr);
nativeAdjusted = options.inverse ? nativeAdjusted.invert() : nativeAdjusted;
if (!opts.xrp_as_drops) {
if (!options.xrp_as_drops) {
// `In a currency exchange, the exchange rate is quoted as the units of the
// counter currency in terms of a single unit of a base currency`. A
// quality is how much taker must `pay` to get ONE `gets` unit thus:
@@ -604,29 +598,34 @@ Amount.prototype.parse_quality = function(quality, counterCurrency, counterIssue
if (this._is_native) {
// pay:$price drops get:1 X
// pay:($price / 1,000,000) XRP get:1 X
nativeAdjusted = adjusted.div(Amount.bi_xns_unit);
nativeAdjusted = nativeAdjusted.divide(bi_xns_unit);
} else if (baseCurrency.is_valid() && baseCurrency.is_native()) {
// pay:$price X get:1 drop
// pay:($price * 1,000,000) X get:1 XRP
nativeAdjusted = adjusted.times(Amount.bi_xns_unit);
nativeAdjusted = nativeAdjusted.multiply(bi_xns_unit);
}
}
this._set_value(nativeAdjusted);
if (opts.reference_date && baseCurrency.is_valid() && baseCurrency.has_interest()) {
var interest = baseCurrency.get_interest_at(opts.reference_date);
this._set_value(this._value.dividedBy(interest.toString()));
if (this._is_native) {
this._set_value(
new XRPValue(nativeAdjusted.round(6, Value.getBNRoundDown()).toString()));
} else {
this._set_value(nativeAdjusted);
}
if (options.reference_date && baseCurrency.is_valid()
&& baseCurrency.has_interest()) {
const interest = baseCurrency.get_interest_at(options.reference_date);
this._set_value(
this._value.divide(new IOUValue(interest.toString())));
}
return this;
};
Amount.prototype.parse_number = function(n) {
this._is_native = false;
this._currency = Currency.from_json(1);
this._issuer = UInt160.from_json(1);
this._set_value(new BigNumber(n));
this._is_native = false;
this._currency = Currency.from_json(1);
this._issuer = UInt160.from_json(1);
this._set_value(new IOUValue(n));
return this;
};
@@ -634,21 +633,22 @@ Amount.prototype.parse_number = function(n) {
Amount.prototype.parse_json = function(j) {
switch (typeof j) {
case 'string':
// .../.../... notation is not a wire format. But allowed for easier testing.
var m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
// .../.../... notation is not a wire format. But allowed for easier
// testing.
const m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
if (m) {
this._currency = Currency.from_json(m[2]);
this._currency = Currency.from_json(m[2]);
if (m[3]) {
this._issuer = UInt160.from_json(m[3]);
this._issuer = UInt160.from_json(m[3]);
} else {
this._issuer = UInt160.from_json('1');
this._issuer = UInt160.from_json('1');
}
this.parse_value(m[1]);
} else {
this.parse_native(j);
this._currency = Currency.from_json('0');
this._issuer = UInt160.from_json('0');
this._currency = Currency.from_json('0');
this._issuer = UInt160.from_json('0');
}
break;
@@ -676,7 +676,7 @@ Amount.prototype.parse_json = function(j) {
break;
default:
this._set_value(new BigNumber(NaN));
this._set_value(new IOUValue(NaN));
}
return this;
@@ -687,16 +687,15 @@ Amount.prototype.parse_json = function(j) {
// - float = with precision 6
// XXX Improvements: disallow leading zeros.
Amount.prototype.parse_native = function(j) {
if (typeof j === 'string' && j.match(/^-?\d*(\.\d{0,6})?$/)) {
var value = new BigNumber(j);
this._is_native = true;
if (j && typeof j === 'string' && !isNaN(j)) {
if (j.indexOf('.') >= 0) {
this._set_value(value);
} else {
this._set_value(value.dividedBy(Amount.bi_xns_unit));
throw new Error('Native amounts must be specified in integer drops');
}
const value = new XRPValue(j);
this._is_native = true;
this._set_value(value.divide(bi_xns_unit));
} else {
this._set_value(new BigNumber(NaN));
this._set_value(new IOUValue(NaN));
}
return this;
@@ -706,12 +705,13 @@ Amount.prototype.parse_native = function(j) {
// Requires _currency to be set!
Amount.prototype.parse_value = function(j) {
this._is_native = false;
this._set_value(new BigNumber(j), BigNumber.ROUND_DOWN);
const newValue = new IOUValue(j, Value.getBNRoundDown());
this._set_value(newValue);
return this;
};
Amount.prototype.set_currency = function(c) {
this._currency = Currency.from_json(c);
this._currency = Currency.from_json(c);
this._is_native = this._currency.is_native();
return this;
};
@@ -737,29 +737,30 @@ Amount.prototype.to_text = function() {
}
if (this._is_native) {
return this._value.times(Amount.bi_xns_unit).toString();
return this._value.multiply(bi_xns_unit).toString();
}
// not native
var offset = this._value.e - 15;
var sign = this._value.isNegative() ? '-' : '';
var mantissa = utils.getMantissaDecimalString(this._value.absoluteValue());
const offset = this._value.getExponent() - 15;
const sign = this._value.isNegative() ? '-' : '';
const mantissa = utils.getMantissa16FromString(
this._value.abs().toString());
if (offset !== 0 && (offset < -25 || offset > -4)) {
// Use e notation.
// XXX Clamp output.
return sign + mantissa.toString() + 'e' + offset.toString();
} else {
var val = '000000000000000000000000000' + mantissa.toString()
+ '00000000000000000000000';
var pre = val.substring(0, offset + 43);
var post = val.substring(offset + 43);
var s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros.
var s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros.
return sign + (s_pre ? s_pre[0] : '0')
+ (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : '');
}
const val = '000000000000000000000000000'
+ mantissa.toString()
+ '00000000000000000000000';
const pre = val.substring(0, offset + 43);
const post = val.substring(offset + 43);
const s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros.
const s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros.
return sign + (s_pre ? s_pre[0] : '0')
+ (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : '');
};
/**
@@ -770,7 +771,7 @@ Amount.prototype.to_text = function() {
* User should not store amount objects after the interest is applied. This is
* intended by display functions such as toHuman().
*
* @param referenceDate {Date|Number} Date based on which demurrage/interest
* @param {Date|Number} referenceDate Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @return {Amount} The amount with interest applied.
*/
@@ -778,50 +779,56 @@ Amount.prototype.applyInterest = function(referenceDate) {
if (!this._currency.has_interest()) {
return this;
}
var interest = this._currency.get_interest_at(referenceDate);
return this._copy(this._value.times(interest.toString()));
const interest = this._currency.get_interest_at(referenceDate);
return this._copy(
this._value.multiply(new IOUValue(interest.toString())));
};
/**
* Format only value in a human-readable format.
*
* @example
* var pretty = amount.to_human({precision: 2});
* let pretty = amount.to_human({precision: 2});
*
* @param opts Options for formatter.
* @param opts.precision {Number} Max. number of digits after decimal point.
* @param opts.min_precision {Number} Min. number of digits after dec. point.
* @param opts.skip_empty_fraction {Boolean} Don't show fraction if it is zero,
* even if min_precision is set.
* @param opts.max_sig_digits {Number} Maximum number of significant digits.
* @param {Object} options Options for formatter.
* @param {Number} options.precision Max. number of digits after decimal point.
* @param {Number} options.min_precision Min. number of digits after dec. point.
* @param {Boolean} options.skip_empty_fraction Don't show fraction if it
* is zero, even if min_precision is set.
* @param {Number} options.max_sig_digits Maximum number of significant digits.
* Will cut fractional part, but never integer part.
* @param opts.group_sep {Boolean|String} Whether to show a separator every n
* @param {Boolean|String} options.group_sep Whether to show a separator every n
* digits, if a string, that value will be used as the separator. Default: ','
* @param opts.group_width {Number} How many numbers will be grouped together,
* default: 3.
* @param opts.signed {Boolean|String} Whether negative numbers will have a
* @param {Number} options.group_width How many numbers will be grouped
* together, default: 3.
* @param {Boolean|String} options.signed Whether negative numbers will have a
* prefix. If String, that string will be used as the prefix. Default: '-'
* @param opts.reference_date {Date|Number} Date based on which demurrage/interest
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
* @param {Date|Number} options.reference_date Date based on which
* demurrage/interest should be applied. Can be given as JavaScript Date or int
* for Ripple epoch.
* @return {String} amount string
*/
Amount.prototype.to_human = function(opts) {
opts = opts || {};
Amount.prototype.to_human = function(options) {
const opts = options || {};
if (!this.is_valid()) {
return 'NaN';
}
/* eslint-disable consistent-this */
// Apply demurrage/interest
var ref = this;
let ref = this;
/* eslint-enable consistent-this */
if (opts.reference_date) {
ref = this.applyInterest(opts.reference_date);
}
var isNegative = ref._value.isNegative();
var valueString = ref._value.abs().toString();
var parts = valueString.split('.');
var int_part = parts[0];
var fraction_part = parts.length === 2 ? parts[1] : '';
const isNegative = ref._value.isNegative();
const valueString = ref._value.abs().toFixed();
const parts = valueString.split('.');
let int_part = parts[0];
let fraction_part = parts.length === 2 ? parts[1] : '';
int_part = int_part.replace(/^0*/, '');
fraction_part = fraction_part.replace(/0*$/, '');
@@ -829,9 +836,9 @@ Amount.prototype.to_human = function(opts) {
if (fraction_part.length || !opts.skip_empty_fraction) {
// Enforce the maximum number of decimal digits (precision)
if (typeof opts.precision === 'number') {
var precision = Math.max(0, opts.precision);
let precision = Math.max(0, opts.precision);
precision = Math.min(precision, fraction_part.length);
var rounded = Number('0.' + fraction_part).toFixed(precision);
const rounded = Number('0.' + fraction_part).toFixed(precision);
if (rounded < 1) {
fraction_part = rounded.substring(2);
@@ -849,16 +856,18 @@ Amount.prototype.to_human = function(opts) {
if (typeof opts.max_sig_digits === 'number') {
// First, we count the significant digits we have.
// A zero in the integer part does not count.
var int_is_zero = Number(int_part) === 0;
var digits = int_is_zero ? 0 : int_part.length;
const int_is_zero = Number(int_part) === 0;
let digits = int_is_zero ? 0 : int_part.length;
// Don't count leading zeros in the fractional part if the integer part is
// zero.
var sig_frac = int_is_zero ? fraction_part.replace(/^0*/, '') : fraction_part;
const sig_frac = int_is_zero
? fraction_part.replace(/^0*/, '')
: fraction_part;
digits += sig_frac.length;
// Now we calculate where we are compared to the maximum
var rounding = digits - opts.max_sig_digits;
let rounding = digits - opts.max_sig_digits;
// If we're under the maximum we want to cut no (=0) digits
rounding = Math.max(rounding, 0);
@@ -883,13 +892,13 @@ Amount.prototype.to_human = function(opts) {
}
if (opts.group_sep !== false) {
var sep = (typeof opts.group_sep === 'string') ? opts.group_sep : ',';
var groups = utils.chunkString(int_part, opts.group_width || 3, true);
const sep = (typeof opts.group_sep === 'string') ? opts.group_sep : ',';
const groups = utils.chunkString(int_part, opts.group_width || 3, true);
int_part = groups.join(sep);
}
var formatted = '';
if(isNegative && opts.signed !== false) {
let formatted = '';
if (isNegative && opts.signed !== false) {
formatted += '-';
}
@@ -899,29 +908,31 @@ Amount.prototype.to_human = function(opts) {
return formatted;
};
Amount.prototype.to_human_full = function(opts) {
opts = opts || {};
var value = this.to_human(opts);
var currency = this._currency.to_human();
var issuer = this._issuer.to_json(opts);
var base = value + '/' + currency;
Amount.prototype.to_human_full = function(options) {
const opts = options || {};
const value = this.to_human(opts);
const currency = this._currency.to_human();
const issuer = this._issuer.to_json(opts);
const base = value + '/' + currency;
return this.is_native() ? base : (base + '/' + issuer);
};
Amount.prototype.to_json = function() {
if (this._is_native) {
return this.to_text();
} else {
var amount_json = {
value : this.to_text(),
currency : this._currency.has_interest() ?
this._currency.to_hex() : this._currency.to_json()
};
if (this._issuer.is_valid()) {
amount_json.issuer = this._issuer.to_json();
}
return amount_json;
}
const amount_json = {
value: this.to_text(),
currency: this._currency.has_interest() ?
this._currency.to_hex() : this._currency.to_json()
};
if (this._issuer.is_valid()) {
amount_json.issuer = this._issuer.to_json();
}
return amount_json;
};
Amount.prototype.to_text_full = function(opts) {
@@ -949,8 +960,8 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) {
return 'Native mismatch.';
}
var type = this._is_native ? 'XRP' : 'Non-XRP';
if (!this._value.isZero() && this._value.negated().equals(d._value)) {
const type = this._is_native ? 'XRP' : 'Non-XRP';
if (!this._value.isZero() && this._value.negate().equals(d._value)) {
return type + ' sign differs.';
}
if (!this._value.equals(d._value)) {
@@ -961,16 +972,19 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) {
return 'Non-XRP currency differs.';
}
if (!ignore_issuer && !this._issuer.equals(d._issuer)) {
return 'Non-XRP issuer differs: ' + d._issuer.to_json() + '/' + this._issuer.to_json();
return 'Non-XRP issuer differs: '
+ d._issuer.to_json()
+ '/'
+ this._issuer.to_json();
}
}
};
exports.Amount = Amount;
exports.Amount = Amount;
// DEPRECATED: Include the corresponding files instead.
exports.Currency = Currency;
exports.Seed = Seed;
exports.UInt160 = UInt160;
exports.Seed = Seed;
exports.UInt160 = UInt160;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -0,0 +1,437 @@
'use strict';
const _ = require('lodash');
const assert = require('assert');
const UInt160 = require('./uint160').UInt160;
const Amount = require('./amount').Amount;
const Utils = require('./orderbookutils');
function assertValidNumber(number, message) {
assert(!_.isNull(number) && !isNaN(number), message);
}
function assertValidLegOneOffer(legOneOffer, message) {
assert(legOneOffer);
assert.strictEqual(typeof legOneOffer, 'object', message);
assert.strictEqual(typeof legOneOffer.TakerPays, 'object', message);
assertValidNumber(legOneOffer.TakerGets, message);
}
function AutobridgeCalculator(currencyGets, currencyPays,
legOneOffers, legTwoOffers, issuerGets, issuerPays) {
this._currencyGets = currencyGets;
this._currencyPays = currencyPays;
this._issuerGets = issuerGets;
this._issuerPays = issuerPays;
this.legOneOffers = _.cloneDeep(legOneOffers);
this.legTwoOffers = _.cloneDeep(legTwoOffers);
this._ownerFundsLeftover = {};
}
/**
* Calculates an ordered array of autobridged offers by quality
*
* @return {Array}
*/
AutobridgeCalculator.prototype.calculate = function() {
let legOnePointer = 0;
let legTwoPointer = 0;
let offersAutobridged = [];
this.clearOwnerFundsLeftover();
while (this.legOneOffers[legOnePointer] && this.legTwoOffers[legTwoPointer]) {
const legOneOffer = this.legOneOffers[legOnePointer];
const legTwoOffer = this.legTwoOffers[legTwoPointer];
const leftoverFunds = this.getLeftoverOwnerFunds(legOneOffer.Account);
let autobridgedOffer;
if (legOneOffer.Account === legTwoOffer.Account) {
this.unclampLegOneOwnerFunds(legOneOffer);
} else if (!legOneOffer.is_fully_funded && !leftoverFunds.is_zero()) {
this.adjustLegOneFundedAmount(legOneOffer);
}
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
if (legOneTakerGetsFunded.is_zero()) {
legOnePointer++;
continue;
}
if (legTwoTakerPaysFunded.is_zero()) {
legTwoPointer++;
continue;
}
if (legOneTakerGetsFunded.compareTo(legTwoTakerPaysFunded) > 0) {
autobridgedOffer = this.getAutobridgedOfferWithClampedLegOne(
legOneOffer,
legTwoOffer
);
legTwoPointer++;
} else if (legTwoTakerPaysFunded.compareTo(legOneTakerGetsFunded) > 0) {
autobridgedOffer = this.getAutobridgedOfferWithClampedLegTwo(
legOneOffer,
legTwoOffer
);
legOnePointer++;
} else {
autobridgedOffer = this.getAutobridgedOfferWithoutClamps(
legOneOffer,
legTwoOffer
);
legOnePointer++;
legTwoPointer++;
}
offersAutobridged.push(autobridgedOffer);
}
return offersAutobridged;
};
/**
* In this case, the output from leg one is greater than the input to leg two.
* Therefore, we must effectively clamp leg one output to leg two input.
*
* @param {Object} legOneOffer
* @param {Object} legTwoOffer
*
* @return {Object}
*/
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegOne =
function(legOneOffer, legTwoOffer) {
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets);
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer);
const autobridgedTakerPays = legTwoTakerPaysFunded.multiply(legOneQuality);
if (legOneOffer.Account === legTwoOffer.Account) {
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer);
const updatedTakerGets = legOneTakerGets.subtract(legTwoTakerPaysFunded);
this.setLegOneTakerGets(legOneOffer, updatedTakerGets);
this.clampLegOneOwnerFunds(legOneOffer);
} else {
// Update funded amount since leg one offer was not completely consumed
const updatedTakerGetsFunded = legOneTakerGetsFunded
.subtract(legTwoTakerPaysFunded);
this.setLegOneTakerGetsFunded(legOneOffer, updatedTakerGetsFunded);
}
return this.formatAutobridgedOffer(
autobridgedTakerGets,
autobridgedTakerPays
);
};
/**
* In this case, the input from leg two is greater than the output to leg one.
* Therefore, we must effectively clamp leg two input to leg one output.
*
* @param {Object} legOneOffer
* @param {Object} legTwoOffer
*
* @return {Object}
*/
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegTwo =
function(legOneOffer, legTwoOffer) {
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
const legTwoQuality = Utils.getOfferQuality(legTwoOffer, this._currencyGets);
const autobridgedTakerGets = legOneTakerGetsFunded.divide(legTwoQuality);
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer);
// Update funded amount since leg two offer was not completely consumed
legTwoOffer.taker_gets_funded = Utils.getOfferTakerGetsFunded(legTwoOffer)
.subtract(autobridgedTakerGets)
.to_text();
legTwoOffer.taker_pays_funded = legTwoTakerPaysFunded
.subtract(legOneTakerGetsFunded)
.to_text();
return this.formatAutobridgedOffer(
autobridgedTakerGets,
autobridgedTakerPays
);
};
/**
* In this case, the output from leg one and the input to leg two are the same.
* We do not need to clamp either.
* @param {Object} legOneOffer
* @param {Object} legTwoOffer
*
* @return {Object}
*/
AutobridgeCalculator.prototype.getAutobridgedOfferWithoutClamps =
function(legOneOffer, legTwoOffer) {
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer);
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer);
return this.formatAutobridgedOffer(
autobridgedTakerGets,
autobridgedTakerPays
);
};
/**
* Clear owner funds leftovers
*/
AutobridgeCalculator.prototype.clearOwnerFundsLeftover = function() {
this._ownerFundsLeftover = {};
};
/**
* Reset owner funds leftovers for an account to 0
*
* @param {String} account
*
* @return {Amount}
*/
AutobridgeCalculator.prototype.resetOwnerFundsLeftover = function(account) {
assert(UInt160.is_valid(account), 'Account is invalid');
this._ownerFundsLeftover[account] = Utils.normalizeAmount('0');
return this._ownerFundsLeftover[account];
};
/**
* Retrieve leftover funds found after clamping leg one by account
*
* @param {String} account
*
* @return {Amount}
*/
AutobridgeCalculator.prototype.getLeftoverOwnerFunds = function(account) {
assert(UInt160.is_valid(account), 'Account is invalid');
let amount = this._ownerFundsLeftover[account];
if (!amount) {
amount = Utils.normalizeAmount('0');
}
return amount;
};
/**
* Add funds to account's leftover funds
*
* @param {String} account
* @param {Amount} amount
*
* @return {Amount}
*/
AutobridgeCalculator.prototype.addLeftoverOwnerFunds =
function(account, amount) {
assert(UInt160.is_valid(account), 'Account is invalid');
assert(amount instanceof Amount, 'Amount is invalid');
this._ownerFundsLeftover[account] = this.getLeftoverOwnerFunds(account)
.add(amount);
return this._ownerFundsLeftover[account];
};
/**
* Set account's leftover funds
*
* @param {String} account
* @param {Amount} amount
*/
AutobridgeCalculator.prototype.setLeftoverOwnerFunds =
function(account, amount) {
assert(UInt160.is_valid(account), 'Account is invalid');
assert(amount instanceof Amount, 'Amount is invalid');
this._ownerFundsLeftover[account] = amount;
};
/**
* Format an autobridged offer and compute synthetic values (e.g. quality)
*
* @param {Amount} takerGets
* @param {Amount} takerPays
*
* @return {Object}
*/
AutobridgeCalculator.prototype.formatAutobridgedOffer =
function(takerGets, takerPays) {
assert(takerGets instanceof Amount, 'Autobridged taker gets is invalid');
assert(takerPays instanceof Amount, 'Autobridged taker pays is invalid');
const autobridgedOffer = {};
const quality = takerPays.divide(takerGets);
autobridgedOffer.TakerGets = {
value: takerGets.to_text(),
currency: this._currencyGets.to_hex(),
issuer: this._issuerGets
};
autobridgedOffer.TakerPays = {
value: takerPays.to_text(),
currency: this._currencyPays.to_hex(),
issuer: this._issuerPays
};
autobridgedOffer.quality = quality.to_text();
autobridgedOffer.taker_gets_funded = autobridgedOffer.TakerGets.value;
autobridgedOffer.taker_pays_funded = autobridgedOffer.TakerPays.value;
autobridgedOffer.autobridged = true;
autobridgedOffer.BookDirectory = Utils.convertOfferQualityToHex(quality);
return autobridgedOffer;
};
/**
* Remove funds clamp on leg one offer. This is necessary when the two offers
* are owned by the same account. In this case, it doesn't matter if offer one
* is not fully funded. Leg one out goes to leg two in and since its the same
* account, an infinite amount can flow.
*
* @param {Object} legOneOffer - IOU:XRP offer
*/
AutobridgeCalculator.prototype.unclampLegOneOwnerFunds = function(legOneOffer) {
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
legOneOffer.initTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
this.setLegOneTakerGetsFunded(
legOneOffer,
Utils.getOfferTakerGets(legOneOffer)
);
};
/**
* Apply clamp back on leg one offer after a round of autobridge calculation
* completes. We must reapply clamps that have been removed because we cannot
* guarantee that the next offer from leg two will also be from the same
* account.
*
* When we reapply, it could happen that the amount of TakerGets left after
* the autobridge calculation is less than the original funded amount. In this
* case, we have extra funds we can use towards unfunded offers with worse
* quality by the same owner.
*
* @param {Object} legOneOffer - IOU:XRP offer
*/
AutobridgeCalculator.prototype.clampLegOneOwnerFunds = function(legOneOffer) {
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
const takerGets = Utils.getOfferTakerGets(legOneOffer);
if (takerGets.compareTo(legOneOffer.initTakerGetsFunded) > 0) {
// After clamping, TakerGets is still greater than initial funded amount
this.setLegOneTakerGetsFunded(legOneOffer, legOneOffer.initTakerGetsFunded);
} else {
const updatedLeftover = legOneOffer.initTakerGetsFunded.subtract(takerGets);
this.setLegOneTakerGetsFunded(legOneOffer, takerGets);
this.addLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover);
}
};
/**
* Increase leg one offer funded amount with extra funds found after applying
* clamp.
*
* @param {Object} legOneOffer - IOU:XRP offer
*/
AutobridgeCalculator.prototype.adjustLegOneFundedAmount =
function(legOneOffer) {
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
assert(!legOneOffer.is_fully_funded, 'Leg one offer cannot be fully funded');
const fundedSum = Utils.getOfferTakerGetsFunded(legOneOffer)
.add(this.getLeftoverOwnerFunds(legOneOffer.Account));
if (fundedSum.compareTo(Utils.getOfferTakerGets(legOneOffer)) >= 0) {
// There are enough extra funds to fully fund the offer
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer);
const updatedLeftover = fundedSum.subtract(legOneTakerGets);
this.setLegOneTakerGetsFunded(legOneOffer, legOneTakerGets);
this.setLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover);
} else {
// There are not enough extra funds to fully fund the offer
this.setLegOneTakerGetsFunded(legOneOffer, fundedSum);
this.resetOwnerFundsLeftover(legOneOffer.Account);
}
};
/**
* Set taker gets funded amount for a IOU:XRP offer. Also calculates taker
* pays funded using offer quality and updates is_fully_funded flag
*
* @param {Object} legOneOffer - IOU:XRP offer
* @param {Amount} takerGetsFunded
*/
AutobridgeCalculator.prototype.setLegOneTakerGetsFunded =
function setLegOneTakerGetsFunded(legOneOffer, takerGetsFunded) {
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
assert(takerGetsFunded instanceof Amount, 'Taker gets funded is invalid');
legOneOffer.taker_gets_funded = takerGetsFunded.to_text();
legOneOffer.taker_pays_funded = takerGetsFunded
.multiply(Utils.getOfferQuality(legOneOffer, this._currencyGets))
.to_text();
if (legOneOffer.taker_gets_funded === legOneOffer.TakerGets.value) {
legOneOffer.is_fully_funded = true;
}
};
/**
* Set taker gets amount for a IOU:XRP offer. Also calculates taker pays
* using offer quality
*
* @param {Object} legOneOffer - IOU:XRP offer
* @param {Amount} takerGets
*/
AutobridgeCalculator.prototype.setLegOneTakerGets =
function(legOneOffer, takerGets) {
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
assert(takerGets instanceof Amount, 'Taker gets funded is invalid');
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets);
legOneOffer.TakerGets = takerGets.to_text();
legOneOffer.TakerPays = takerGets.multiply(legOneQuality);
};
module.exports = AutobridgeCalculator;

154
src/core/base.js Normal file
View File

@@ -0,0 +1,154 @@
'use strict';
const _ = require('lodash');
const sjcl = require('./utils').sjcl;
const utils = require('./utils');
const extend = require('extend');
const convertBase = require('./baseconverter');
const Base = {};
const alphabets = Base.alphabets = {
ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz',
bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
};
extend(Base, {
VER_NONE: 1,
VER_NODE_PUBLIC: 28,
VER_NODE_PRIVATE: 32,
VER_ACCOUNT_ID: 0,
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]);
});
// --> 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);
};
// --> input: String
// <-- array of bytes or undefined.
Base.decode = function(input, alpha) {
if (typeof input !== 'string') {
return undefined;
}
try {
return this.encoders[alpha || 'ripple'].decode(input);
} 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);
};
// --> input : String
// <-- NaN || sjcl.bn
Base.decode_check = function(version, input, alphabet) {
const buffer = Base.decode(input, alphabet);
if (!buffer || buffer.length < 5) {
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;

32
src/core/baseconverter.js Normal file
View File

@@ -0,0 +1,32 @@
'use strict';
function normalize(digitArray) {
while (digitArray[0] === 0) {
digitArray.shift();
}
return digitArray;
}
function divmod(digitArray, base, divisor) {
var remainder = 0;
var quotient = [];
for (var j = 0; j < digitArray.length; j++) {
var temp = remainder * base + parseInt(digitArray[j], 10);
quotient.push(Math.floor(temp / divisor));
remainder = temp % divisor;
}
return {quotient: normalize(quotient), remainder: remainder};
}
function convertBase(digitArray, fromBase, toBase) {
var result = [];
var dividend = digitArray;
while (dividend.length > 0) {
var qr = divmod(dividend, fromBase, toBase);
result.unshift(qr.remainder);
dividend = qr.quotient;
}
return normalize(result);
}
module.exports = convertBase;

View File

@@ -1,13 +1,19 @@
'use strict';
/*eslint no-multi-spaces:0,space-in-brackets:0,key-spacing:0,comma-spacing:0*/
/**
* Data type map.
*
* Mapping of type ids to data types. The type id is specified by the high
*
* For reference, see rippled's definition:
* https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol/SField.cpp
* https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol
* /SField.cpp
*/
var TYPES_MAP = exports.types = [
void(0),
exports.types = [
undefined,
// Common
'Int16', // 1
@@ -20,11 +26,11 @@ var TYPES_MAP = exports.types = [
'Account', // 8
// 9-13 reserved
void(0), // 9
void(0), // 10
void(0), // 11
void(0), // 12
void(0), // 13
undefined, // 9
undefined, // 10
undefined, // 11
undefined, // 12
undefined, // 13
'Object', // 14
'Array', // 15
@@ -42,7 +48,7 @@ var TYPES_MAP = exports.types = [
* Mapping of field type id to field type name.
*/
var FIELDS_MAP = exports.fields = {
const FIELDS_MAP = exports.fields = {
// Common types
1: { // Int16
1: 'LedgerEntryType',
@@ -151,7 +157,7 @@ var FIELDS_MAP = exports.fields = {
8: 'RegularKey'
},
14: { // Object
1: void(0), //end of Object
1: undefined, // end of Object
2: 'TransactionMetaData',
3: 'CreatedNode',
4: 'DeletedNode',
@@ -163,7 +169,7 @@ var FIELDS_MAP = exports.fields = {
10: 'Memo'
},
15: { // Array
1: void(0), //end of Array
1: undefined, // end of Array
2: 'SigningAccounts',
3: 'TxnSignatures',
4: 'Signatures',
@@ -196,7 +202,7 @@ var FIELDS_MAP = exports.fields = {
}
};
var INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
let INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
Object.keys(FIELDS_MAP).forEach(function(k1) {
Object.keys(FIELDS_MAP[k1]).forEach(function(k2) {
@@ -204,12 +210,11 @@ Object.keys(FIELDS_MAP).forEach(function(k1) {
});
});
const REQUIRED = exports.REQUIRED = 0,
OPTIONAL = exports.OPTIONAL = 1,
DEFAULT = exports.DEFAULT = 2;
var REQUIRED = exports.REQUIRED = 0,
OPTIONAL = exports.OPTIONAL = 1,
DEFAULT = exports.DEFAULT = 2;
var base = [
const base = [
[ 'TransactionType' , REQUIRED ],
[ 'Flags' , OPTIONAL ],
[ 'SourceTag' , OPTIONAL ],
@@ -219,7 +224,9 @@ var base = [
[ 'Fee' , REQUIRED ],
[ 'OperationLimit' , OPTIONAL ],
[ 'SigningPubKey' , REQUIRED ],
[ 'TxnSignature' , OPTIONAL ]
[ 'TxnSignature' , OPTIONAL ],
[ 'AccountTxnID' , OPTIONAL ],
[ 'Memos' , OPTIONAL ]
];
exports.tx = {
@@ -229,7 +236,9 @@ exports.tx = {
[ 'WalletSize' , OPTIONAL ],
[ 'MessageKey' , OPTIONAL ],
[ 'Domain' , OPTIONAL ],
[ 'TransferRate' , OPTIONAL ]
[ 'TransferRate' , OPTIONAL ],
[ 'SetFlag' , OPTIONAL ],
[ 'ClearFlag' , OPTIONAL ]
]),
TrustSet: [20].concat(base, [
[ 'LimitAmount' , OPTIONAL ],
@@ -239,13 +248,14 @@ exports.tx = {
OfferCreate: [7].concat(base, [
[ 'TakerPays' , REQUIRED ],
[ 'TakerGets' , REQUIRED ],
[ 'Expiration' , OPTIONAL ]
[ 'Expiration' , OPTIONAL ],
[ 'OfferSequence' , OPTIONAL ]
]),
OfferCancel: [8].concat(base, [
[ 'OfferSequence' , REQUIRED ]
]),
SetRegularKey: [5].concat(base, [
[ 'RegularKey' , REQUIRED ]
[ 'RegularKey' , OPTIONAL ]
]),
Payment: [0].concat(base, [
[ 'Destination' , REQUIRED ],
@@ -271,16 +281,25 @@ exports.tx = {
EnableFeature: [100].concat(base, [
[ 'Feature' , REQUIRED ]
]),
EnableAmendment: [100].concat(base, [
[ 'Amendment' , REQUIRED ]
]),
SetFee: [101].concat(base, [
[ 'Features' , REQUIRED ],
[ 'BaseFee' , REQUIRED ],
[ 'ReferenceFeeUnits' , REQUIRED ],
[ 'ReserveBase' , REQUIRED ],
[ 'ReserveIncrement' , REQUIRED ]
]),
TicketCreate: [10].concat(base, [
[ 'Target' , OPTIONAL ],
[ 'Expiration' , OPTIONAL ]
]),
TicketCancel: [11].concat(base, [
[ 'TicketID' , REQUIRED ]
])
};
var sleBase = [
const sleBase = [
['LedgerIndex', OPTIONAL],
['LedgerEntryType', REQUIRED],
['Flags', REQUIRED]
@@ -414,5 +433,8 @@ exports.ter = {
tecNO_TARGET : 138,
tecNO_PERMISSION : 139,
tecNO_ENTRY : 140,
tecINSUFFICIENT_RESERVE : 141
tecINSUFFICIENT_RESERVE : 141,
tecNEED_MASTER_KEY : 142,
tecDST_TAG_NEEDED : 143,
tecINTERNAL : 144
};

View File

@@ -1,4 +1,6 @@
var extend = require('extend');
'use strict';
var extend = require('extend');
var UInt160 = require('./uint160').UInt160;
var utils = require('./utils');
var Float = require('./ieee754').Float;
@@ -16,8 +18,7 @@ var Currency = extend(function() {
// 3-letter code: ...
// XXX Should support hex, C++ doesn't currently allow it.
this._value = NaN;
this._value = NaN;
this._update();
}, UInt160);
@@ -32,25 +33,37 @@ Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
* Examples:
*
* USD => currency
* USD - Dollar => currency with optional full currency name
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate per year
* USD - Dollar => currency with optional full currency
* name
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate
* per year
* XAU - Gold (-0.5%pa) => Optionally allowed full currency name
* USD (1%pa) => US dollars with 1% effective interest per year
* USD (1%pa) => US dollars with 1% effective interest
* per year
* INR - Indian Rupees => Optional full currency name with spaces
* TYX - 30-Year Treasuries => Optional full currency with numbers and a dash
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers, dash and interest rate
* TYX - 30-Year Treasuries => Optional full currency with numbers
* and a dash
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers,
* dash and interest rate
*
* The regular expression below matches above cases, broken down for better understanding:
* The regular expression below matches above cases, broken down for better
* understanding:
*
* ^\s* // start with any amount of whitespace
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217
* (\s*-\s*[- \w]+) // optional full currency name following the dash after currency code,
* full currency code can contain letters, numbers and dashes
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and . notation (-0.5%pa)
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3
* digit numeric currency-code. See ISO 4217
* (\s*-\s*[- \w]+) // optional full currency name following the dash
* after currency code, full currency code can
* contain letters, numbers and dashes
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and
* . notation (-0.5%pa)
* \s*$ // end with any amount of whitespace
*
*/
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
/*eslint-disable max-len*/
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9\<\>\(\)\{\}\[\]\|\?\!\@\#\$\%\^\&]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
/*eslint-enable max-len*/
Currency.from_json = function(j, shouldInterpretXrpAsIou) {
return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
@@ -58,39 +71,65 @@ Currency.from_json = function(j, shouldInterpretXrpAsIou) {
Currency.from_human = function(j, opts) {
return (new Currency().parse_human(j, opts));
}
};
// this._value = NaN on error.
Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
this._value = NaN;
switch (typeof j) {
case 'string':
if (j instanceof Currency) {
this._value = j.copyTo({})._value;
this._update();
return this;
}
// if an empty string is given, fall back to XRP
switch (typeof j) {
case 'number':
if (!isNaN(j)) {
this.parse_number(j);
}
break;
case 'string':
if (!j || j === '0') {
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
// Empty string or XRP
this.parse_hex(shouldInterpretXrpAsIou
? Currency.HEX_CURRENCY_BAD
: Currency.HEX_ZERO);
break;
}
if (j === '1') {
// 'no currency'
this.parse_hex(Currency.HEX_ONE);
break;
}
if (/^[A-F0-9]{40}$/.test(j)) {
// Hex format
this.parse_hex(j);
break;
}
// match the given string to see if it's in an allowed format
var matches = String(j).match(this.human_RE);
var matches = j.match(this.human_RE);
if (matches) {
var currencyCode = matches[1];
// for the currency 'XRP' case
// we drop everything else that could have been provided
// e.g. 'XRP - Ripple'
if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) {
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
this.parse_hex(shouldInterpretXrpAsIou
? Currency.HEX_CURRENCY_BAD
: Currency.HEX_ZERO);
// early break, we can't have interest on XRP
break;
}
// the full currency is matched as it is part of the valid currency format, but not stored
// the full currency is matched as it is part of the valid currency
// format, but not stored
// var full_currency = matches[2] || '';
var interest = matches[3] || '';
@@ -117,25 +156,28 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
currencyData[2] = currencyCode.charCodeAt(1) & 0xff;
currencyData[3] = currencyCode.charCodeAt(2) & 0xff;
// byte 5-8 are for reference date, but should always be 0 so we won't fill it
// byte 5-8 are for reference date, but should always be 0 so we
// won't fill it
// byte 9-16 are for the interest
percentage = parseFloat(percentage[0]);
// the interest or demurrage is expressed as a yearly (per annum) value
// the interest or demurrage is expressed as a yearly (per annum)
// value
var secondsPerYear = 31536000; // 60 * 60 * 24 * 365
// Calculating the interest e-fold
// 0.5% demurrage is expressed 0.995, 0.005 less than 1
// 0.5% interest is expressed as 1.005, 0.005 more than 1
var interestEfold = secondsPerYear / Math.log(1 + percentage/100);
var interestEfold = secondsPerYear / Math.log(1 + percentage / 100);
var bytes = Float.toIEEE754Double(interestEfold);
for (var i=0; i<=bytes.length; i++) {
for (var i = 0; i <= bytes.length; i++) {
currencyData[8 + i] = bytes[i] & 0xff;
}
// the last 4 bytes are reserved for future use, so we won't fill those
// the last 4 bytes are reserved for future use, so we won't fill
// those
} else {
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
@@ -144,21 +186,6 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
}
this.parse_bytes(currencyData);
} else {
this.parse_hex(j);
}
break;
case 'number':
if (!isNaN(j)) {
this.parse_number(j);
}
break;
case 'object':
if (j instanceof Currency) {
this._value = j.copyTo({})._value;
this._update();
}
break;
}
@@ -166,7 +193,6 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
return this;
};
Currency.prototype.parse_human = function(j) {
return this.parse_json(j);
};
@@ -176,6 +202,7 @@ Currency.prototype.parse_human = function(j) {
*
* You should never need to call this.
*/
Currency.prototype._update = function() {
var bytes = this.to_bytes();
@@ -183,7 +210,7 @@ Currency.prototype._update = function() {
var isZeroExceptInStandardPositions = true;
if (!bytes) {
return 'XRP';
return;
}
this._native = false;
@@ -192,8 +219,9 @@ Currency.prototype._update = function() {
this._interest_period = NaN;
this._iso_code = '';
for (var i=0; i<20; i++) {
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || bytes[i]===0);
for (var i = 0; i < 20; i++) {
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
&& (i === 12 || i === 13 || i === 14 || bytes[i] === 0);
}
if (isZeroExceptInStandardPositions) {
@@ -201,7 +229,7 @@ Currency.prototype._update = function() {
+ String.fromCharCode(bytes[13])
+ String.fromCharCode(bytes[14]);
if (this._iso_code === '\0\0\0') {
if (this._iso_code === '\u0000\u0000\u0000') {
this._native = true;
this._iso_code = 'XRP';
}
@@ -215,8 +243,8 @@ Currency.prototype._update = function() {
this._type = 1;
this._interest_start = (bytes[4] << 24) +
(bytes[5] << 16) +
(bytes[6] << 8) +
(bytes[7] );
(bytes[6] << 8) +
(bytes[7]);
this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16));
}
};
@@ -230,7 +258,8 @@ Currency.prototype.parse_bytes = function(byte_array) {
var isZeroExceptInStandardPositions = true;
for (var i=0; i<20; i++) {
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byte_array[0]===0)
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
&& (i===12 || i===13 || i===14 || byte_array[0]===0)
}
if (isZeroExceptInStandardPositions) {
@@ -260,20 +289,25 @@ Currency.prototype.is_native = function() {
};
/**
* Whether this currency is an interest-bearing/demurring currency.
* @return {Boolean} whether this currency is an interest-bearing currency
*/
Currency.prototype.has_interest = function() {
return this._type === 1 && !isNaN(this._interest_start) && !isNaN(this._interest_period);
return this._type === 1
&& !isNaN(this._interest_start)
&& !isNaN(this._interest_period);
};
/**
*
* @param referenceDate - number of seconds since the Ripple Epoch (0:00 on January 1, 2000 UTC)
* used to calculate the interest over provided interval
* pass in one years worth of seconds to ge the yearly interest
* @returns {number} - interest for provided interval, can be negative for demurred currencies
* @param {number} referenceDate number of seconds since the Ripple Epoch
* (0:00 on January 1, 2000 UTC) used to calculate the
* interest over provided interval pass in one years
* worth of seconds to ge the yearly interest
* @returns {number} interest for provided interval, can be negative for
* demurred currencies
*/
Currency.prototype.get_interest_at = function(referenceDate, decimals) {
Currency.prototype.get_interest_at = function(referenceDate) {
if (!this.has_interest()) {
return 0;
}
@@ -288,18 +322,20 @@ Currency.prototype.get_interest_at = function(referenceDate, decimals) {
}
// calculate interest by e-fold number
return Math.exp((referenceDate - this._interest_start) / this._interest_period);
return Math.exp((referenceDate - this._interest_start)
/ this._interest_period);
};
Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals) {
Currency.prototype.get_interest_percentage_at
= function(referenceDate, decimals) {
var interest = this.get_interest_at(referenceDate, decimals);
// convert to percentage
var interest = (interest*100)-100;
var decimalMultiplier = decimals ? Math.pow(10,decimals) : 100;
interest = (interest * 100) - 100;
var decimalMultiplier = decimals ? Math.pow(10, decimals) : 100;
// round to two decimals behind the dot
return Math.round(interest*decimalMultiplier) / decimalMultiplier;
return Math.round(interest * decimalMultiplier) / decimalMultiplier;
};
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
@@ -307,9 +343,9 @@ Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals
// We could be doing further checks into the internal format of the
// currency data, since there are some values that are invalid.
//
//Currency.prototype.is_valid = function() {
// Currency.prototype.is_valid = function() {
// return UInt.prototype.is_valid() && ...;
//};
// };
Currency.prototype.to_json = function(opts) {
if (!this.is_valid()) {
@@ -317,28 +353,35 @@ Currency.prototype.to_json = function(opts) {
return 'XRP';
}
var opts = opts || {};
if (!opts) {
opts = {};
}
var currency;
var fullName = opts && opts.full_name ? ' - ' + opts.full_name : '';
opts.show_interest = opts.show_interest !== void(0) ? opts.show_interest : this.has_interest();
opts.show_interest = opts.show_interest !== undefined
? opts.show_interest
: this.has_interest();
if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) {
currency = this._iso_code + fullName;
if (opts.show_interest) {
var decimals = !isNaN(opts.decimals) ? opts.decimals : void(0);
var interestPercentage = this.has_interest() ? this.get_interest_percentage_at(this._interest_start + 3600 * 24 * 365, decimals) : 0;
var decimals = !isNaN(opts.decimals) ? opts.decimals : undefined;
var interestPercentage = this.has_interest()
? this.get_interest_percentage_at(
this._interest_start + 3600 * 24 * 365, decimals
)
: 0;
currency += ' (' + interestPercentage + '%pa)';
}
} else {
// Fallback to returning the raw currency hex
currency = this.to_hex();
// XXX This is to maintain backwards compatibility, but it is very, very odd
// behavior, so we should deprecate it and get rid of it as soon as
// possible.
// XXX This is to maintain backwards compatibility, but it is very, very
// odd behavior, so we should deprecate it and get rid of it as soon as
// possible.
if (currency === Currency.HEX_ONE) {
currency = 1;
}
@@ -357,5 +400,3 @@ Currency.prototype.get_iso = function() {
};
exports.Currency = Currency;
// vim:sw=2:sts=2:ts=8:et

View File

@@ -1,3 +1,10 @@
'use strict';
// TODO: move in helpers from serializedtypes to utils
function toBytes(n) {
return [n >>> 24, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];
}
/**
* Prefix for hashing functions.
*
@@ -12,14 +19,18 @@
*/
// transaction plus signature to give transaction ID
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
// transaction plus metadata
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
// inner node in tree
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
// leaf node in tree
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
// inner transaction to sign
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
// inner transaction to sign (TESTNET)
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
Object.keys(exports).forEach(function(k) {
exports[k + '_BYTES'] = toBytes(exports[k]);
});

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