├── test ├── fixtures │ ├── response-ticker-pairs.json │ ├── response-ticker-funding.json │ ├── response-ws2-server-ticker-funding.json │ ├── response-ws2-server-order-book-P0.json │ ├── response-ws2-server-order-book-P1.json │ ├── response-ws2-server-trades.json │ ├── response-ws2-server-order-book-R0.json │ ├── response-ws-1-orderbook-R0.json │ ├── response-trades-pairs.json │ └── response-trades-funding.json ├── lib │ ├── util │ │ ├── is_snapshot.js │ │ └── is_class.js │ └── transports │ │ ├── channels.js │ │ └── ws2-integration.js ├── examples │ ├── setup.js │ ├── debug_table.js │ └── args_from_env.js └── index.js ├── .npmignore ├── docs ├── fonts │ ├── Montserrat │ │ ├── Montserrat-Bold.eot │ │ ├── Montserrat-Bold.ttf │ │ ├── Montserrat-Bold.woff │ │ ├── Montserrat-Bold.woff2 │ │ ├── Montserrat-Regular.eot │ │ ├── Montserrat-Regular.ttf │ │ ├── Montserrat-Regular.woff │ │ └── Montserrat-Regular.woff2 │ └── Source-Sans-Pro │ │ ├── sourcesanspro-light-webfont.eot │ │ ├── sourcesanspro-light-webfont.ttf │ │ ├── sourcesanspro-light-webfont.woff │ │ ├── sourcesanspro-light-webfont.woff2 │ │ ├── sourcesanspro-regular-webfont.eot │ │ ├── sourcesanspro-regular-webfont.ttf │ │ ├── sourcesanspro-regular-webfont.woff │ │ └── sourcesanspro-regular-webfont.woff2 ├── scripts │ ├── polyfill.js │ ├── nav.js │ ├── linenumber.js │ ├── prettify │ │ ├── lang-css.js │ │ ├── Apache-License-2.0.txt │ │ └── prettify.js │ ├── commonNav.js │ ├── collapse.js │ └── search.js ├── styles │ ├── prettify.css │ └── jsdoc.css ├── util_ws2.js.html └── util_precision.js.html ├── lib └── util │ ├── is_snapshot.js │ ├── index.js │ ├── is_class.js │ ├── ws2.js │ └── precision.js ├── examples ├── util │ ├── debug.js │ ├── debug_table.js │ ├── arg_from_cli.js │ ├── args_from_env.js │ └── setup.js ├── rest2 │ ├── status.js │ ├── symbols.js │ ├── currencies.js │ ├── key_permissions.js │ ├── margin_info.js │ ├── transfer.js │ ├── symbol-details.js │ ├── funding_info.js │ ├── funding_loans.js │ ├── submit_funding_offer.js │ ├── funding_credits.js │ ├── funding_offers.js │ ├── ledgers.js │ ├── wallet.js │ ├── trade-history.js │ ├── list_open_orders.js │ ├── movements.js │ ├── tickers.js │ ├── claim_positions.js │ ├── order-history.js │ ├── positions.js │ ├── close_positions.js │ ├── submit_order.js │ └── wallets.js ├── ws2 │ ├── notifications.js │ ├── funding_info.js │ ├── notify_ui.js │ ├── liquidations.js │ ├── tickers.js │ ├── auth.js │ ├── cancel_all_buf.js │ ├── calc.js │ ├── ob_checksum.js │ ├── trades.js │ ├── info_events.js │ ├── order_books.js │ ├── order_tif.js │ ├── candles.js │ ├── orders.js │ ├── oc_multi.js │ ├── oco-order.js │ ├── sequencing.js │ ├── cancel_all.js │ ├── order_book_viz.js │ ├── ox_multi.js │ └── atomic_order_update.js └── ws2_manager.js ├── .travis.yml ├── .github ├── ISSUE_TEMPLATE └── PULL_REQUEST_TEMPLATE ├── .gitignore ├── .husky └── pre-commit ├── .jsdoc.json ├── LICENSE.md ├── .istanbul.yml ├── package.json ├── index.js ├── README.md └── CHANGELOG /test/fixtures/response-ticker-pairs.json: -------------------------------------------------------------------------------- 1 | [1781.8,3.10227283,1781.9,1.44527318,-35.7,-0.0196,1781.8,13402.66689773,1834.2,1726.3] 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | docs 3 | coverage 4 | examples 5 | .github 6 | .nyc_output 7 | .travis.yml 8 | .istanbul.yml 9 | .jsdoc.json 10 | -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Montserrat/Montserrat-Bold.eot -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Montserrat/Montserrat-Bold.ttf -------------------------------------------------------------------------------- /lib/util/is_snapshot.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const isSnapshot = msg => msg[0] && Array.isArray(msg[0]) 4 | 5 | module.exports = isSnapshot 6 | -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Montserrat/Montserrat-Bold.woff -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Montserrat/Montserrat-Bold.woff2 -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Montserrat/Montserrat-Regular.eot -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Montserrat/Montserrat-Regular.ttf -------------------------------------------------------------------------------- /test/fixtures/response-ticker-funding.json: -------------------------------------------------------------------------------- 1 | [0.0009239,0.00071,30,5000,0.0009239,2,44568.06495174,0.00044901,0.3207,0.001849,14032554.7966796,0,0] 2 | -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Montserrat/Montserrat-Regular.woff -------------------------------------------------------------------------------- /docs/fonts/Montserrat/Montserrat-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Montserrat/Montserrat-Regular.woff2 -------------------------------------------------------------------------------- /examples/util/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Proxy to allow external stubbing 4 | const debug = require('debug') 5 | module.exports = { get: () => debug } 6 | -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff -------------------------------------------------------------------------------- /test/fixtures/response-ws2-server-ticker-funding.json: -------------------------------------------------------------------------------- 1 | [22,[0.00078458,0.00075,30,3045.59478528,0.0007825,2,2335880.06705868,-0.0000674,-0.0793,0.0007825,19326761.40360705,0,0]] 2 | -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitfinexcom/bitfinex-api-node/HEAD/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - "stable" 6 | 7 | install: 8 | - npm install 9 | 10 | script: 11 | - npm run lint 12 | - npm run unit 13 | -------------------------------------------------------------------------------- /lib/util/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const isClass = require('./is_class') 4 | const isSnapshot = require('./is_snapshot') 5 | 6 | module.exports = { 7 | isClass, 8 | isSnapshot 9 | } 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | #### Issue type 2 | - [ ] bug 3 | - [ ] missing functionality 4 | - [ ] performance 5 | - [ ] feature request 6 | 7 | #### Brief description 8 | 9 | #### Steps to reproduce 10 | - 11 | 12 | ##### Additional Notes: 13 | - 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | /test/test_api_keys.json 4 | /build 5 | npm-debug.log 6 | source/images 7 | .DS_Store 8 | todo 9 | package-lock.json 10 | .env 11 | dist/ 12 | .nyc_output 13 | coverage/ 14 | .vim 15 | tags 16 | .undodir 17 | Session.vim 18 | -------------------------------------------------------------------------------- /docs/scripts/polyfill.js: -------------------------------------------------------------------------------- 1 | //IE Fix, src: https://www.reddit.com/r/programminghorror/comments/6abmcr/nodelist_lacks_foreach_in_internet_explorer/ 2 | if (typeof(NodeList.prototype.forEach)!==typeof(alert)){ 3 | NodeList.prototype.forEach=Array.prototype.forEach; 4 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | ### Description: 2 | ... 3 | 4 | ### Breaking changes: 5 | - [ ] 6 | 7 | ### New features: 8 | - [ ] 9 | 10 | ### Fixes: 11 | - [ ] 12 | 13 | ### PR status: 14 | - [ ] Version bumped 15 | - [ ] Change-log updated 16 | - [ ] Documentation updated 17 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # only run on added/modified files 4 | git diff --name-only --cached --diff-filter=AM -- "*.js" | \ 5 | while read -r file 6 | do 7 | if [ -f "$file" ]; then # don't run on deleted files 8 | npx standard "$file" 9 | fi 10 | done 11 | -------------------------------------------------------------------------------- /lib/util/is_class.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _isFunction = require('lodash/isFunction') 4 | 5 | const isClass = (f) => { 6 | return ( 7 | (_isFunction(f)) && 8 | (/^class\s/.test(Function.prototype.toString.call(f))) 9 | ) 10 | } 11 | 12 | module.exports = isClass 13 | -------------------------------------------------------------------------------- /lib/util/ws2.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _findLast = require('lodash/findLast') 4 | 5 | /** 6 | * Resolves the message payload; useful for getting around sequence numbers 7 | * 8 | * @param {Array} msg - message to parse 9 | * @returns {Array} payload - undefined if not found 10 | */ 11 | module.exports = (msg = []) => { 12 | return _findLast(msg, i => Array.isArray(i)) 13 | } 14 | -------------------------------------------------------------------------------- /docs/scripts/nav.js: -------------------------------------------------------------------------------- 1 | function scrollToNavItem() { 2 | var path = window.location.href.split('/').pop().replace(/\.html/, ''); 3 | document.querySelectorAll('nav a').forEach(function(link) { 4 | var href = link.attributes.href.value.replace(/\.html/, ''); 5 | if (path === href) { 6 | link.scrollIntoView({block: 'center'}); 7 | return; 8 | } 9 | }) 10 | } 11 | 12 | scrollToNavItem(); 13 | -------------------------------------------------------------------------------- /examples/rest2/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { RESTv2 } = require('../../index') 4 | const { debug } = require('../util/setup') 5 | 6 | async function execute () { 7 | const rest = new RESTv2() 8 | debug('fetching platform status...') 9 | 10 | const status = await rest.status() 11 | 12 | debug(status === 0 13 | ? 'Platform currently under maintenance' 14 | : 'Platform operating normally' 15 | ) 16 | } 17 | 18 | execute() 19 | -------------------------------------------------------------------------------- /examples/rest2/symbols.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { RESTv2 } = require('../../index') 4 | const { debug } = require('../util/setup') 5 | 6 | async function execute () { 7 | const rest = new RESTv2({ 8 | transform: true 9 | }) 10 | debug('fetching symbol list...') 11 | 12 | const symbols = await rest.symbols() 13 | 14 | debug('read %d symbols', symbols.length) 15 | debug('%s', symbols.map(s => `t${s.toUpperCase()}`).join(', ')) 16 | } 17 | 18 | execute() 19 | -------------------------------------------------------------------------------- /examples/ws2/notifications.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | apiKey, 9 | apiSecret, 10 | transform: true 11 | }) 12 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 13 | await ws.open() 14 | await ws.auth() 15 | 16 | ws.onNotification({ type: '*' }, (n) => { 17 | debug('recv notification: %j', n.toJS()) 18 | }) 19 | } 20 | 21 | execute() 22 | -------------------------------------------------------------------------------- /examples/rest2/currencies.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _chunk = require('lodash/chunk') 4 | const { RESTv2 } = require('../../index') 5 | const { debug } = require('../util/setup') 6 | 7 | async function execute () { 8 | const rest = new RESTv2() 9 | debug('fetching currency list...') 10 | 11 | const currencies = await rest.currencies() 12 | 13 | debug('received %d currencies', currencies[0].length) 14 | 15 | debug('') 16 | _chunk(currencies[0], 10).forEach((currencyChunk) => { 17 | debug('%s', currencyChunk.join(', ')) 18 | }) 19 | debug('') 20 | } 21 | 22 | execute() 23 | -------------------------------------------------------------------------------- /examples/ws2/funding_info.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | const symbol = 'fUSD' 6 | 7 | async function execute () { 8 | const ws = new WSv2({ 9 | apiKey, 10 | apiSecret, 11 | transform: true 12 | }) 13 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 14 | await ws.open() 15 | await ws.auth() 16 | 17 | ws.onFundingInfoUpdate({}, fi => { 18 | debug('fl: %j', fi.toJS()) 19 | ws.close() 20 | }) 21 | 22 | ws.requestCalc([`funding_sym_${symbol}`]) 23 | } 24 | 25 | execute() 26 | -------------------------------------------------------------------------------- /examples/ws2/notify_ui.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | apiKey, 9 | apiSecret, 10 | transform: true 11 | }) 12 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 13 | await ws.open() 14 | await ws.auth() 15 | 16 | ws.notifyUI({ 17 | type: 'success', 18 | message: 'This is a test notification sent via the WSv2 API' 19 | }) 20 | 21 | debug('notification sent') 22 | await ws.close() 23 | } 24 | 25 | execute() 26 | -------------------------------------------------------------------------------- /test/lib/util/is_snapshot.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const assert = require('assert') 5 | 6 | const { isSnapshot } = require('../../../lib/util') 7 | 8 | describe('isSnapshot - detects snapshots by data structure', () => { 9 | it('returns false for heartbeats', () => { 10 | assert.strictEqual(isSnapshot(['hb']), false) 11 | }) 12 | 13 | it('returns false simple lists (data updates)', () => { 14 | assert.strictEqual(isSnapshot([1337]), false) 15 | }) 16 | 17 | it('returns true for nested lists (snapshots)', () => { 18 | assert.strictEqual(isSnapshot([['a'], ['b']]), true) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /examples/rest2/key_permissions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { RESTv2 } = require('../../index') 4 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 5 | 6 | async function execute () { 7 | const rest = new RESTv2({ 8 | apiKey, 9 | apiSecret, 10 | transform: true 11 | }) 12 | debug('fetching permissions') 13 | 14 | const perms = await rest.keyPermissions() 15 | 16 | const rows = perms.map(({ key, read, write }) => [ 17 | key.toUpperCase(), read ? 'Y' : 'N', write ? 'Y' : 'N' 18 | ]) 19 | 20 | debugTable({ 21 | rows, 22 | headers: ['Scope', 'Read', 'Write'] 23 | }) 24 | } 25 | 26 | execute() 27 | -------------------------------------------------------------------------------- /.jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": false, 4 | "dictionaries": ["jsdoc"] 5 | }, 6 | "source": { 7 | "include": ["lib", "LICENSE.md", "README.md"], 8 | "includePattern": ".js$", 9 | "excludePattern": "(node_modules/|docs)" 10 | }, 11 | "plugins": [ 12 | "plugins/markdown" 13 | ], 14 | "templates": { 15 | "cleverLinks": false, 16 | "monospaceLinks": true, 17 | "useLongnameInNav": false, 18 | "showInheritedInNav": true 19 | }, 20 | "opts": { 21 | "destination": "./docs/", 22 | "encoding": "utf8", 23 | "recurse": true, 24 | "template": "./node_modules/docdash" 25 | }, 26 | "package": "" 27 | } 28 | -------------------------------------------------------------------------------- /examples/util/debug_table.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Table = require('cli-table3') 4 | 5 | /** 6 | * Generates a CLI table and logs it to the console 7 | * 8 | * @param {object} args - arguments 9 | * @param {object} args.rows - data 10 | * @param {object} args.headers - column headers 11 | * @param {object} args.debug - log function 12 | * @returns {string} table 13 | */ 14 | module.exports = ({ rows, headers, debug }) => { 15 | const t = new Table({ 16 | head: headers, 17 | colWidths: [] // auto-compute 18 | }) 19 | 20 | rows.forEach(r => t.push(r)) 21 | 22 | const str = t.toString() 23 | str.split('\n').map(l => debug('%s', l)) 24 | return str 25 | } 26 | -------------------------------------------------------------------------------- /examples/ws2/liquidations.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Liquidations } = require('bfx-api-node-models') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | 7 | async function execute () { 8 | const ws = new WSv2({ 9 | apiKey, 10 | apiSecret 11 | }) 12 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 13 | await ws.open() 14 | 15 | ws.onStatus({ key: 'liq:global' }, (data) => { 16 | data.forEach(liq => ( 17 | debug('liquidation: %s', new Liquidations(liq).toString()) 18 | )) 19 | }) 20 | 21 | await ws.subscribeStatus('liq:global') 22 | } 23 | 24 | execute() 25 | -------------------------------------------------------------------------------- /examples/ws2/tickers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | transform: true 9 | }) 10 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 11 | await ws.open() 12 | 13 | ws.onTicker({ symbol: 'tETHUSD' }, (ticker) => { 14 | debug('ETH/USD ticker: %j', ticker.toJS()) 15 | }) 16 | 17 | ws.onTicker({ symbol: 'fUSD' }, (ticker) => { 18 | debug('fUSD ticker: %j', ticker.toJS()) 19 | }) 20 | 21 | await ws.subscribeTicker('tETHUSD') 22 | await ws.subscribeTicker('fUSD') 23 | await ws.close() 24 | } 25 | 26 | execute() 27 | -------------------------------------------------------------------------------- /examples/ws2/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | apiKey, 9 | apiSecret 10 | }) 11 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 12 | 13 | // register a callback for any order snapshot that comes in (account orders) 14 | ws.onOrderSnapshot({}, (orders) => { 15 | debug(`order snapshot: ${JSON.stringify(orders, null, 2)}`) 16 | }) 17 | 18 | await ws.open() 19 | debug('open') 20 | 21 | await ws.auth() 22 | debug('authenticated') 23 | 24 | // do something with authenticated ws stream 25 | await ws.close() 26 | } 27 | 28 | execute() 29 | -------------------------------------------------------------------------------- /test/lib/util/is_class.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const assert = require('assert') 5 | const { TradingTicker } = require('bfx-api-node-models') 6 | const { isClass } = require('../../../lib/util') 7 | 8 | describe('isClass', () => { 9 | it('returns true for classes', () => { 10 | assert(isClass(TradingTicker)) 11 | }) 12 | 13 | it('returns false for functions', () => { 14 | assert(!isClass(() => {})) 15 | }) 16 | 17 | it('returns false for class instances', () => { 18 | const t = new TradingTicker() 19 | assert(!isClass(t)) 20 | }) 21 | 22 | it('returns false for primitives', () => { 23 | assert(!isClass(42)) 24 | assert(!isClass('42')) 25 | assert(!isClass({})) 26 | assert(!isClass([])) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /examples/rest2/margin_info.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount } = require('bfx-api-node-util') 4 | const { RESTv2 } = require('../../index') 5 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 6 | 7 | async function execute () { 8 | const rest = new RESTv2({ 9 | apiKey, 10 | apiSecret, 11 | transform: true 12 | }) 13 | debug('fetching margin info...') 14 | 15 | const info = await rest.marginInfo() 16 | const { userPL, userSwaps, marginBalance, marginNet } = info 17 | 18 | debug('') 19 | debug('Swaps: %d', userSwaps) 20 | debug('P/L: %s', prepareAmount(userPL)) 21 | debug('Balance: %s', prepareAmount(marginBalance)) 22 | debug('Net Balance: %s', prepareAmount(marginNet)) 23 | debug('') 24 | } 25 | 26 | execute() 27 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /examples/util/arg_from_cli.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _isEmpty = require('lodash/isEmpty') 4 | const _isFunction = require('lodash/isFunction') 5 | 6 | /** 7 | * Grabs an argument from the arguments list if we've been executed via node or 8 | * npm 9 | * 10 | * @param {number} index - starting after invocation (2) 11 | * @param {string} def - fallback value if none found/not supported 12 | * @param {Function?} parser - optional, used to process value if provided 13 | * @returns {string} value 14 | */ 15 | module.exports = (index, def, parser) => { 16 | const val = /node/.test(process.argv[0]) || /npm/.test(process.argv[0]) 17 | ? _isEmpty(process.argv[2 + index]) ? def : process.argv[2 + index] 18 | : def 19 | 20 | return _isFunction(parser) 21 | ? parser(val) 22 | : val 23 | } 24 | -------------------------------------------------------------------------------- /examples/ws2/cancel_all_buf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | apiKey, 9 | apiSecret, 10 | transform: true, 11 | orderOpBufferDelay: 250 12 | }) 13 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 14 | await ws.open() 15 | await ws.auth() 16 | 17 | ws.onOrderSnapshot({}, async (snapshot) => { 18 | if (snapshot.length === 0) { 19 | debug('no orders to cancel') 20 | } else { 21 | debug('canceling %d orders', snapshot.length) 22 | 23 | await ws.cancelOrders(snapshot) 24 | debug('cancelled all orders') 25 | } 26 | await ws.close() 27 | }) 28 | } 29 | 30 | execute() 31 | -------------------------------------------------------------------------------- /test/examples/setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const assert = require('assert') 5 | const _isObject = require('lodash/isObject') 6 | const _isFunction = require('lodash/isFunction') 7 | 8 | const { args, debug, debugTable, readline } = require('../../examples/util/setup') 9 | 10 | describe('setup', () => { 11 | it('provides a debugger', () => { 12 | assert.ok(_isObject(args), 'setup doesnt provide a tooling object') 13 | assert.ok(_isFunction(debug), 'setup doesnt provide a debug() instance') 14 | assert.ok(_isFunction(debugTable), 'setup doesnt provide a debugTable() instance') 15 | }) 16 | 17 | it('provides a readline instance', () => { 18 | assert.ok(_isFunction(readline.questionAsync), 'no readline instance provided') 19 | }) 20 | }).timeout(10 * 1000) // timeout for travis 21 | -------------------------------------------------------------------------------- /examples/rest2/transfer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { RESTv2 } = require('../../index') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | 6 | async function execute () { 7 | const rest = new RESTv2({ 8 | apiKey, 9 | apiSecret, 10 | transform: true 11 | }) 12 | const { fromType, fromCCY, toType, toCCY, amount } = { 13 | fromType: 'deposit', 14 | fromCCY: 'USD', 15 | toType: 'trading', 16 | toCCY: 'USD', 17 | amount: 1 18 | } 19 | 20 | debug( 21 | 'transferring %f from %s %s to %s %s', 22 | amount, fromType, fromCCY, toType, toCCY 23 | ) 24 | 25 | await rest.transfer({ 26 | amount: `${amount}`, 27 | from: fromType, 28 | currency: fromCCY, 29 | to: toType, 30 | currencyTo: toCCY 31 | }) 32 | 33 | debug('done!') 34 | } 35 | 36 | execute() 37 | -------------------------------------------------------------------------------- /examples/ws2/calc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | apiKey, 9 | apiSecret 10 | }) 11 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 12 | await ws.open() 13 | await ws.auth() 14 | 15 | await new Promise(resolve => setTimeout(resolve, 5 * 1000)) 16 | 17 | ws.requestCalc([ 18 | 'margin_sym_tBTCUSD', 19 | 'position_tBTCUSD', 20 | 'wallet_margin_BTC', 21 | 'wallet_funding_USD' 22 | ]) 23 | 24 | // Watch log output for balance update packets (wu, miu, etc) 25 | debug('sent calc, closing in 3s...') 26 | 27 | await new Promise(resolve => setTimeout(resolve, 3 * 1000)) 28 | await ws.close() 29 | } 30 | 31 | execute() 32 | -------------------------------------------------------------------------------- /examples/ws2/ob_checksum.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | const SYMBOL = 'tXRPBTC' 7 | const PRECISION = 'P0' 8 | const LENGTH = '25' 9 | 10 | async function execute () { 11 | const ws = new WSv2({ 12 | transform: true, 13 | manageOrderbooks: true // managed OBs are verified against incoming checksums 14 | }) 15 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 16 | await ws.open() 17 | 18 | ws.onOrderBookChecksum({ 19 | symbol: SYMBOL, 20 | prec: PRECISION, 21 | len: LENGTH 22 | }, cs => { 23 | debug('recv valid cs for %s:%s:%s %d', SYMBOL, PRECISION, LENGTH, cs) 24 | }) 25 | 26 | await ws.enableFlag(WSv2.flags.CHECKSUM) 27 | await ws.subscribeOrderBook(SYMBOL, PRECISION, LENGTH) 28 | } 29 | 30 | execute() 31 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /examples/ws2/trades.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | transform: true 9 | }) 10 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 11 | await ws.open() 12 | 13 | const market = 'tBTCUSD' 14 | 15 | if (market[0] === 't') { 16 | ws.onTradeEntry({ symbol: market }, (trade) => { 17 | debug('trade on %s: %s', market, trade.toString()) 18 | }) 19 | } else { 20 | ws.onFundingTradeEntry({ symbol: market }, (trade) => { 21 | debug('funding trade: %s', trade.toString()) 22 | }) 23 | } 24 | 25 | ws.onAccountTradeEntry({ symbol: market }, (trade) => { 26 | debug('account trade: %s', trade.toString()) 27 | }) 28 | 29 | await ws.subscribeTrades(market) 30 | } 31 | 32 | execute() 33 | -------------------------------------------------------------------------------- /examples/ws2/info_events.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | apiKey, 9 | apiSecret, 10 | autoReconnect: true 11 | }) 12 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 13 | await ws.open() 14 | await ws.auth() 15 | 16 | ws.onMaintenanceStart(() => { 17 | debug('info: maintenance period started') 18 | // pause activity untill further notice 19 | }) 20 | 21 | ws.onMaintenanceEnd(() => { 22 | debug('info: maintenance period ended') 23 | // resume activity 24 | }) 25 | 26 | ws.onServerRestart(() => { 27 | debug('info: bitfinex ws server restarted') 28 | // await ws.reconnect() // if not using autoReconnect 29 | }) 30 | await ws.close() 31 | } 32 | 33 | execute() 34 | -------------------------------------------------------------------------------- /examples/rest2/symbol-details.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { RESTv2 } = require('../../index') 4 | const { debug, debugTable } = require('../util/setup') 5 | 6 | async function execute () { 7 | const rest = new RESTv2({ 8 | transform: true 9 | }) 10 | debug('fetching symbol details...') 11 | 12 | const details = await rest.symbolDetails() 13 | 14 | debugTable({ 15 | headers: [ 16 | 'Pair', 'Initial Margin', 'Min Margin', 'Max Order', 17 | 'Min Order', 'Margin' 18 | ], 19 | 20 | rows: details.map(({ 21 | pair, initialMargin, minimumMargin, // eslint-disable-line 22 | maximumOrderSize, minimumOrderSize, margin // eslint-disable-line 23 | }) => [ 24 | pair.toUpperCase(), initialMargin, minimumMargin, // eslint-disable-line 25 | maximumOrderSize, minimumOrderSize, margin ? 'Y' : 'N' // eslint-disable-line 26 | ]) 27 | }) 28 | } 29 | 30 | execute() 31 | -------------------------------------------------------------------------------- /test/fixtures/response-ws2-server-order-book-P0.json: -------------------------------------------------------------------------------- 1 | [300,[[1921.8,1,0.17974513],[1920.1,1,0.8809],[1920,4,3.61],[1919.2,1,1.4],[1919,2,1.09],[1918.4,1,1.58208707],[1918.1,2,1.05],[1918,2,1.03418],[1917.7,1,1.57],[1917.5,1,0.03],[1917,3,6.84188197],[1916.5,1,4.197819],[1916.4,1,1.6],[1916.2,1,0.1],[1915.7,1,1.58652],[1915,2,2.54],[1914.6,2,1.590214],[1914.5,2,0.43327161],[1914.3,1,0.1],[1914.2,2,1.580264],[1913.6,2,1.547905],[1913,1,0.012048],[1912.9,1,1.11],[1912.8,1,1.24],[1912.4,1,0.1],[1921.9,1,-5.12],[1922,2,-5.2353],[1922.4,1,-0.52759063],[1922.6,2,-1.271148],[1923,1,-1.4],[1923.4,1,-0.92932246],[1923.6,1,-5.2026],[1923.7,1,-1.98603296],[1923.8,1,-0.1],[1924,1,-1],[1924.5,1,-1.5],[1925.2,1,-1],[1925.6,1,-0.01],[1925.7,1,-0.1],[1925.8,1,-1.32],[1925.9,1,-1.4679],[1926.4,1,-0.62607888],[1926.5,1,-1.45],[1926.7,1,-6.2431],[1927,1,-1.74938207],[1927.3,1,-1.447971],[1927.6,1,-0.1],[1927.8,3,-2.58442],[1927.9,1,-1.6],[1928.3,1,-0.1]]] 2 | -------------------------------------------------------------------------------- /examples/rest2/funding_info.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount } = require('bfx-api-node-util') 4 | const argFromCLI = require('../util/arg_from_cli') 5 | const { RESTv2 } = require('../../index') 6 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 7 | 8 | async function execute () { 9 | const rest = new RESTv2({ 10 | apiKey, 11 | apiSecret, 12 | transform: true 13 | }) 14 | const symbol = argFromCLI(0, 'fUSD') 15 | 16 | debug('fetching funding info for %s', symbol) 17 | 18 | const flu = await rest.fundingInfo(symbol) 19 | const [,, [yieldLoan, yieldLend, durationLoan, durationLend]] = flu 20 | 21 | debugTable({ 22 | headers: [ 23 | 'Symbol', 'Yield Loan', 'Yield Lend', 'Duration Loan', 'Duration Lend' 24 | ], 25 | rows: [[ 26 | symbol, prepareAmount(yieldLoan), prepareAmount(yieldLend), durationLoan, 27 | durationLend 28 | ]] 29 | }) 30 | } 31 | 32 | execute() 33 | -------------------------------------------------------------------------------- /examples/rest2/funding_loans.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount } = require('bfx-api-node-util') 4 | const argFromCLI = require('../util/arg_from_cli') 5 | const { RESTv2 } = require('../../index') 6 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 7 | 8 | async function execute () { 9 | const rest = new RESTv2({ 10 | apiKey, 11 | apiSecret, 12 | transform: true 13 | }) 14 | const symbol = argFromCLI(0, 'fUSD') 15 | 16 | debug('fetching funding loans for %s', symbol) 17 | 18 | const fls = await rest.fundingLoans(symbol) 19 | 20 | if (fls.length === 0) { 21 | debug('none available') 22 | } else { 23 | debugTable({ 24 | headers: ['Symbol', 'Amount', 'Status', 'Rate', 'Period', 'Renew'], 25 | rows: fls.map(fl => [ 26 | fl.symbol, prepareAmount(fl.amount), fl.status, fl.rate * 100, 27 | fl.period, fl.renew ? 'Y' : 'N' 28 | ]) 29 | }) 30 | } 31 | } 32 | 33 | execute() 34 | -------------------------------------------------------------------------------- /examples/rest2/submit_funding_offer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { FundingOffer } = require('bfx-api-node-models') 4 | const { RESTv2 } = require('../../index') 5 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 6 | 7 | const CLOSE_DELAY_MS = 5 * 1000 8 | 9 | async function execute () { 10 | const rest = new RESTv2({ 11 | apiKey, 12 | apiSecret, 13 | transform: true 14 | }) 15 | const fo = new FundingOffer({ 16 | type: 'LIMIT', 17 | symbol: 'fUSD', 18 | rate: 0.0120000, 19 | amount: 120, 20 | period: 2 21 | }, rest) 22 | 23 | debug('submitting: %s', fo.toString()) 24 | 25 | try { 26 | await fo.submit() 27 | } catch (e) { 28 | return debug('failed: %s', e.message) 29 | } 30 | 31 | debug('done. closing in %ds...', CLOSE_DELAY_MS / 1000) 32 | 33 | await new Promise(resolve => setTimeout(resolve, CLOSE_DELAY_MS)) 34 | await fo.close() 35 | 36 | debug('offer closed') 37 | } 38 | 39 | execute() 40 | -------------------------------------------------------------------------------- /examples/rest2/funding_credits.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount } = require('bfx-api-node-util') 4 | const argFromCLI = require('../util/arg_from_cli') 5 | const { RESTv2 } = require('../../index') 6 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 7 | 8 | async function execute () { 9 | const rest = new RESTv2({ 10 | apiKey, 11 | apiSecret, 12 | transform: true 13 | }) 14 | const symbol = argFromCLI(0, 'fUSD') 15 | 16 | debug('fetching funding credits for %s', symbol) 17 | 18 | const fcs = await rest.fundingCredits(symbol) 19 | 20 | if (fcs.length === 0) { 21 | debug('none available') 22 | } else { 23 | debugTable({ 24 | headers: ['Symbol', 'Amount', 'Status', 'Rate', 'Period', 'Renew'], 25 | rows: fcs.map(fc => [ 26 | fc.symbol, prepareAmount(fc.amount), fc.status, fc.rate * 100, 27 | fc.period, fc.renew ? 'Y' : 'N' 28 | ]) 29 | }) 30 | } 31 | } 32 | 33 | execute() 34 | -------------------------------------------------------------------------------- /examples/rest2/funding_offers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount } = require('bfx-api-node-util') 4 | const argFromCLI = require('../util/arg_from_cli') 5 | const { RESTv2 } = require('../../index') 6 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 7 | 8 | async function execute () { 9 | const rest = new RESTv2({ 10 | apiKey, 11 | apiSecret, 12 | transform: true 13 | }) 14 | const symbol = argFromCLI(0, 'fUSD') 15 | 16 | debug('fetching funding offers for %s', symbol) 17 | 18 | const fos = await rest.fundingOffers(symbol) 19 | 20 | if (fos.length === 0) { 21 | debug('none available') 22 | } else { 23 | debugTable({ 24 | headers: ['Symbol', 'Amount', 'Status', 'Rate', 'Period', 'Renew'], 25 | rows: fos.map(fo => [ 26 | fo.symbol, prepareAmount(fo.amount), fo.status, fo.rate * 100, 27 | fo.period, fo.renew ? 'Y' : 'N' 28 | ]) 29 | }) 30 | } 31 | } 32 | 33 | execute() 34 | -------------------------------------------------------------------------------- /examples/ws2/order_books.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | transform: true, // auto-transform array OBs to OrderBook objects 9 | manageOrderBooks: true // tell the ws client to maintain full sorted OBs 10 | }) 11 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 12 | await ws.open() 13 | 14 | let lastMidPrice = -1 15 | let midPrice 16 | 17 | // 'ob' is a full OrderBook instance, with sorted arrays 'bids' & 'asks' 18 | ws.onOrderBook({ symbol: 'tBTCUSD' }, (ob) => { 19 | midPrice = ob.midPrice() 20 | 21 | if (midPrice !== lastMidPrice) { 22 | debug( 23 | 'BTCUSD mid price: %d (bid: %d, ask: %d)', 24 | midPrice, ob.bids[0][0], ob.asks[0][0] 25 | ) 26 | } 27 | 28 | lastMidPrice = midPrice 29 | }) 30 | 31 | await ws.subscribeOrderBook('tBTCUSD') 32 | } 33 | 34 | execute() 35 | -------------------------------------------------------------------------------- /test/examples/debug_table.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const assert = require('assert') 5 | const debugTable = require('../../examples/util/debug_table') 6 | 7 | describe('debugTable', () => { 8 | it('throws an error if row, header, and column counts don\'t match', () => { 9 | try { 10 | debugTable({ 11 | rows: [[1]], 12 | headers: ['', ''], 13 | widths: [20, 20, 20], 14 | debug: () => {} 15 | }) 16 | assert.fail('no error was thrown') 17 | } catch (e) { 18 | assert.ok(true) 19 | } 20 | }) 21 | 22 | it('prints the table out line by line, and returns it as a multi-line string', () => { 23 | let debugLineCount = 0 24 | 25 | const str = debugTable({ 26 | rows: [[1, 1, 1], [2, 2, 2], [3, 3, 3]], 27 | headers: ['', '', ''], 28 | widths: [20, 20, 20], 29 | debug: () => debugLineCount++ 30 | }) 31 | 32 | assert.strictEqual(str.split('\n').length, debugLineCount) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/fixtures/response-ws2-server-order-book-P1.json: -------------------------------------------------------------------------------- 1 | [31,[[1779,1,42.11518492],[1776,1,0.65],[1775,6,4.08689264],[1774,5,4.426],[1773,5,5.50443213],[1772,6,7.79304654],[1771,4,7.1125],[1770,4,10.32053939],[1769,2,2.3227],[1768,3,2.3928],[1767,2,2.6782],[1766,5,2.29046402],[1765,4,8.4198058],[1764,3,4.1],[1763,3,2.36],[1762,5,27.5415973],[1761,3,1.319864],[1760,8,6.61289691],[1759,5,2.63016337],[1758,2,1.34098036],[1757,4,13.8345],[1756,4,6.23052701],[1755,2,1.24530005],[1754,4,6.049529],[1753,3,4.503684],[1780,14,-33.0779031],[1781,4,-1.58324806],[1782,2,-2.4],[1783,5,-4.17911621],[1784,12,-28.4504812],[1785,14,-9.47617966],[1786,12,-64.80149549],[1787,8,-13.94001992],[1788,10,-45.1987484],[1789,12,-21.28205024],[1790,11,-8.97454227],[1791,8,-12.36341796],[1792,10,-11.23215846],[1793,7,-26.68999992],[1794,6,-5.64348706],[1795,7,-63.0999998],[1796,9,-70.67113304],[1797,7,-14.99802176],[1798,13,-40.14042763],[1799,13,-28.45315875],[1800,23,-430.5491778],[1801,4,-2.56285988],[1802,5,-1.20007984],[1803,8,-2.46653968],[1804,5,-95.64373988]]] 2 | -------------------------------------------------------------------------------- /examples/util/args_from_env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _isString = require('lodash/isString') 4 | const _isEmpty = require('lodash/isEmpty') 5 | const { SocksProxyAgent } = require('socks-proxy-agent') 6 | 7 | const validArg = v => _isString(v) && !_isEmpty(v) 8 | 9 | /** 10 | * Grabs RESTv2/WSv2 constructor arguments from the environment, configuring 11 | * the api credentials, connection agent, and connection URL 12 | * 13 | * @param {string?} urlKey - name of env var holding the connection URL 14 | * @returns {object} envArgs 15 | */ 16 | module.exports = (urlKey) => { 17 | const { API_KEY, API_SECRET, SOCKS_PROXY_URL } = process.env 18 | const URL = process.env[urlKey] 19 | const agent = validArg(SOCKS_PROXY_URL) && new SocksProxyAgent(SOCKS_PROXY_URL) 20 | const envArgs = {} 21 | 22 | if (agent) envArgs.agent = agent 23 | if (validArg(URL)) envArgs.url = URL 24 | if (validArg(API_KEY)) envArgs.apiKey = API_KEY 25 | if (validArg(API_SECRET)) envArgs.apiSecret = API_SECRET 26 | 27 | return envArgs 28 | } 29 | -------------------------------------------------------------------------------- /examples/rest2/ledgers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount } = require('bfx-api-node-util') 4 | const { RESTv2 } = require('../../index') 5 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 6 | const argFromCLI = require('../util/arg_from_cli') 7 | 8 | async function execute () { 9 | const rest = new RESTv2({ 10 | apiKey, 11 | apiSecret, 12 | transform: true 13 | }) 14 | const params = { 15 | ccy: argFromCLI(0, 'all') 16 | } 17 | const ccy = params.ccy === 'all' ? null : params.ccy 18 | 19 | debug('fetching ledger entries for %s...', ccy || 'all currencies') 20 | 21 | const entries = await rest.ledgers(ccy) 22 | const rows = entries.map(e => [ 23 | e.id, e.currency, new Date(e.mts).toLocaleString(), prepareAmount(e.amount), 24 | prepareAmount(e.balance), e.description 25 | ]) 26 | 27 | debugTable({ 28 | rows, 29 | headers: [ 30 | 'Entry ID', 'Currency', 'Timestamp', 'Amount', 'Balance', 'Description' 31 | ] 32 | }) 33 | } 34 | 35 | execute() 36 | -------------------------------------------------------------------------------- /examples/ws2/order_tif.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Order } = require('bfx-api-node-models') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | 7 | const o = new Order({ 8 | cid: Date.now(), 9 | symbol: 'tBTCUSD', 10 | price: 17833.5, 11 | amount: -0.02, 12 | type: Order.type.LIMIT, 13 | tif: '2019-03-08 15:00:00' 14 | }) 15 | 16 | async function execute () { 17 | const ws = new WSv2({ 18 | apiKey, 19 | apiSecret, 20 | transform: true 21 | }) 22 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 23 | await ws.open() 24 | await ws.auth() 25 | 26 | o.registerListeners(ws) 27 | 28 | o.on('update', () => debug('updated: %s', o.toString())) 29 | o.on('close', () => debug('order closed: %s', o.status)) 30 | 31 | debug('submitting order %d', o.cid) 32 | await o.submit() 33 | 34 | debug( 35 | 'got submit confirmation for order %d [%d] [tif: %d]', 36 | o.cid, o.id, o.mtsTIF 37 | ) 38 | await ws.close() 39 | } 40 | 41 | execute() 42 | -------------------------------------------------------------------------------- /test/lib/transports/channels.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | 'use strict' 4 | 5 | const assert = require('assert') 6 | 7 | const WSv2 = require('../../../lib/transports/ws2') 8 | 9 | const API_KEY = 'dummy' 10 | const API_SECRET = 'dummy' 11 | 12 | const createTestWSv2Instance = (params = {}) => { 13 | return new WSv2({ 14 | apiKey: API_KEY, 15 | apiSecret: API_SECRET, 16 | url: 'ws://localhost:9997', 17 | ...params 18 | }) 19 | } 20 | 21 | describe('WSv2 channels', () => { 22 | it('numeric and string channel ids work', () => { 23 | const ws = createTestWSv2Instance() 24 | 25 | ws._channelMap = { 26 | 83297: { 27 | event: 'subscribed', 28 | channel: 'book', 29 | chanId: 83297, 30 | symbol: 'tADAUSD', 31 | prec: 'P0', 32 | freq: 'F0', 33 | len: '25', 34 | pair: 'ADAUSD' 35 | } 36 | } 37 | 38 | assert.strictEqual(ws.hasChannel(83297), true) 39 | assert.strictEqual(ws.hasChannel('83297'), true) 40 | assert.strictEqual(ws.hasChannel('1337'), false) 41 | assert.strictEqual(ws.hasChannel(1337), false) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 bitfinexcom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/scripts/commonNav.js: -------------------------------------------------------------------------------- 1 | if (typeof fetch === 'function') { 2 | const init = () => { 3 | if (typeof scrollToNavItem !== 'function') return false 4 | scrollToNavItem() 5 | // hideAllButCurrent not always loaded 6 | if (typeof hideAllButCurrent === 'function') hideAllButCurrent() 7 | return true 8 | } 9 | fetch('./nav.inc.html') 10 | .then(response => response.ok ? response.text() : `${response.url} => ${response.status} ${response.statusText}`) 11 | .then(body => { 12 | document.querySelector('nav').innerHTML += body 13 | // nav.js should be quicker to load than nav.inc.html, a fallback just in case 14 | return init() 15 | }) 16 | .then(done => { 17 | if (done) return 18 | let i = 0 19 | ;(function waitUntilNavJs () { 20 | if (init()) return 21 | if (i++ < 100) return setTimeout(waitUntilNavJs, 300) 22 | console.error(Error('nav.js not loaded after 30s waiting for it')) 23 | })() 24 | }) 25 | .catch(error => console.error(error)) 26 | } else { 27 | console.error(Error('Browser too old to display commonNav (remove commonNav docdash option)')) 28 | } 29 | -------------------------------------------------------------------------------- /examples/ws2/candles.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 4 | const WSv2 = require('../../lib/transports/ws2') 5 | 6 | async function execute () { 7 | const ws = new WSv2({ 8 | apiKey, 9 | apiSecret, 10 | manageCandles: true, // enable candle dataset persistence/management 11 | transform: true // converts ws data arrays to Candle models (and others) 12 | }) 13 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 14 | await ws.open() 15 | 16 | const market = 'tBTCUSD' 17 | const tf = '5m' 18 | const candleKey = `trade:${tf}:${market}` 19 | let prevTS = null 20 | 21 | ws.onCandle({ key: candleKey }, (candles) => { 22 | if (candles[0].mts === prevTS) { 23 | return 24 | } 25 | 26 | const c = candles[1] // report previous candle 27 | 28 | debug('%s %s open: %f, high: %f, low: %f, close: %f, volume: %f', 29 | candleKey, new Date(c.mts).toLocaleTimeString(), 30 | c.open, c.high, c.low, c.close, c.volume 31 | ) 32 | 33 | prevTS = candles[0].mts 34 | }) 35 | 36 | await ws.subscribeCandles(candleKey) 37 | } 38 | 39 | execute() 40 | -------------------------------------------------------------------------------- /examples/rest2/wallet.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { RESTv2 } = require('../../index') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | 6 | async function execute () { 7 | const rest = new RESTv2({ 8 | apiKey, 9 | apiSecret, 10 | transform: true 11 | }) 12 | debug('Submitting new order...') 13 | 14 | // get new deposit address 15 | const address = await rest.getDepositAddress({ 16 | wallet: 'exchange', 17 | method: 'bitcoin', 18 | opRenew: 0 19 | }) 20 | 21 | debug(`new wallet address ${address}`) 22 | 23 | // transfer between accounts 24 | const transferConfirmation = await rest.transfer({ 25 | from: 'exchange', 26 | to: 'margin', 27 | amount: 10, 28 | currency: 'BTC', 29 | currencyTo: 'BTC' 30 | }) 31 | 32 | debug('transfer confirmed: %j', transferConfirmation) 33 | 34 | // withdraw 35 | const withdrawalConfirmation = await rest.withdraw({ 36 | wallet: 'exchange', 37 | method: 'bitcoin', 38 | amount: 2, 39 | address: '1MUz4VMYui5qY1mxUiG8BQ1Luv6tqkvaiL' 40 | }) 41 | 42 | debug('withdraw confirmed: %j', withdrawalConfirmation) 43 | } 44 | 45 | execute() 46 | -------------------------------------------------------------------------------- /docs/styles/prettify.css: -------------------------------------------------------------------------------- 1 | .pln { 2 | color: #ddd; 3 | } 4 | 5 | /* string content */ 6 | .str { 7 | color: #61ce3c; 8 | } 9 | 10 | /* a keyword */ 11 | .kwd { 12 | color: #fbde2d; 13 | } 14 | 15 | /* a comment */ 16 | .com { 17 | color: #aeaeae; 18 | } 19 | 20 | /* a type name */ 21 | .typ { 22 | color: #8da6ce; 23 | } 24 | 25 | /* a literal value */ 26 | .lit { 27 | color: #fbde2d; 28 | } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #ddd; 33 | } 34 | 35 | /* lisp open bracket */ 36 | .opn { 37 | color: #000000; 38 | } 39 | 40 | /* lisp close bracket */ 41 | .clo { 42 | color: #000000; 43 | } 44 | 45 | /* a markup tag name */ 46 | .tag { 47 | color: #8da6ce; 48 | } 49 | 50 | /* a markup attribute name */ 51 | .atn { 52 | color: #fbde2d; 53 | } 54 | 55 | /* a markup attribute value */ 56 | .atv { 57 | color: #ddd; 58 | } 59 | 60 | /* a declaration */ 61 | .dec { 62 | color: #EF5050; 63 | } 64 | 65 | /* a variable name */ 66 | .var { 67 | color: #c82829; 68 | } 69 | 70 | /* a function name */ 71 | .fun { 72 | color: #4271ae; 73 | } 74 | 75 | /* Specify class=linenums on a pre to get line numbering */ 76 | ol.linenums { 77 | margin-top: 0; 78 | margin-bottom: 0; 79 | padding-bottom: 2px; 80 | } 81 | -------------------------------------------------------------------------------- /examples/util/setup.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const dotenv = require('dotenv') 4 | const Readline = require('readline-promise').default 5 | const argsFromEnv = require('./args_from_env') 6 | const debugTableUtil = require('./debug_table') 7 | const D = require('./debug').get() 8 | const debug = D('>') 9 | debug.enabled = true 10 | 11 | dotenv.config() 12 | 13 | /** 14 | * Log a table to the console 15 | * 16 | * @param {object} args - arguments 17 | * @param {object[]} args.rows - data, can be specified as 2nd param 18 | * @param {string[]} args.headers - column labels 19 | * @param {number[]} args.widths - column widths 20 | * @param {object[]} extraRows - optional row spec as 2nd param 21 | */ 22 | const debugTable = ({ rows = [], headers, widths }, extraRows = []) => { 23 | debug('') 24 | debugTableUtil({ 25 | rows: [...rows, ...extraRows], 26 | headers, 27 | widths, 28 | debug 29 | }) 30 | debug('') 31 | } 32 | module.exports = { 33 | get args () { 34 | return argsFromEnv() 35 | }, 36 | debug, 37 | debugTable, 38 | get readline () { 39 | return Readline.createInterface({ 40 | input: process.stdin, 41 | output: process.stdout, 42 | terminal: false 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/fixtures/response-ws2-server-trades.json: -------------------------------------------------------------------------------- 1 | [31,[[32288059,1494971706000,-0.00042864,1773.9],[32288058,1494971706000,-0.1,1775.6],[32288057,1494971706000,-0.04470386,1775.8],[32288054,1494971696000,-0.34748184,1776],[32288052,1494971693000,-0.08015163,1777.4],[32288047,1494971690000,-0.01984837,1777.4],[32288044,1494971687000,-0.18976977,1775.7],[32288040,1494971686000,0.26486383,1780.3],[32288039,1494971686000,0.95246765,1779.7],[32288038,1494971686000,0.1,1779.2],[32288037,1494971686000,0.8906,1779.1],[32288036,1494971686000,0.915224,1778.6],[32288035,1494971686000,1.6989623,1778.5],[32288034,1494971686000,0.53929331,1778.3],[32288033,1494971686000,1.9835,1778.2],[32288032,1494971686000,0.60187396,1778.1],[32288031,1494971686000,0.05321495,1778.1],[32288030,1494971684000,-1.79759839,1775.8],[32288029,1494971684000,-0.12600507,1777.4],[32288025,1494971683000,-0.05321495,1777.4],[32288024,1494971682000,0.1,1777.4],[32288023,1494971682000,0.04471204,1777.1],[32288022,1494971682000,0.1,1775.6],[32288021,1494971682000,0.67606794,1775.5],[32288014,1494971682000,-0.05321495,1775.4],[32288011,1494971681000,0.01840201,1775.5],[32288007,1494971678000,-0.21410901,1775.2],[32288006,1494971678000,-0.01986347,1775.2],[32288005,1494971678000,-0.78074942,1775.2],[32288003,1494971677000,-0.1544203,1775.2]]] 2 | -------------------------------------------------------------------------------- /examples/rest2/trade-history.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount, preparePrice } = require('bfx-api-node-util') 4 | const _isEmpty = require('lodash/isEmpty') 5 | const { RESTv2 } = require('../../index') 6 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 7 | 8 | const START = Date.now() - (30 * 24 * 60 * 60 * 1000 * 1000) 9 | const END = Date.now() 10 | const LIMIT = 25 11 | 12 | async function execute () { 13 | const rest = new RESTv2({ 14 | apiKey, 15 | apiSecret, 16 | transform: true 17 | }) 18 | const symbol = 'tBTCUSD' 19 | 20 | if (_isEmpty(symbol)) { 21 | return debug('symbol required') 22 | } 23 | 24 | debug('fetching 30d trade history for %s...', symbol) 25 | 26 | const trades = await rest.accountTrades(symbol, START, END, LIMIT) 27 | 28 | if (trades.length === 0) { 29 | return debug('no historical trades for %s', symbol) 30 | } 31 | 32 | debugTable({ 33 | headers: [ 34 | 'Trade ID', 'Order ID', 'Created', 'Exec Amount', 'Exec Price', 'Fee' 35 | ], 36 | 37 | rows: trades.map(t => [ 38 | t.id, t.orderID, new Date(t.mtsCreate).toLocaleString(), 39 | prepareAmount(t.execAmount), preparePrice(t.execPrice), 40 | `${t.fee} ${t.feeCurrency}` 41 | ]) 42 | }) 43 | } 44 | 45 | execute() 46 | -------------------------------------------------------------------------------- /lib/util/precision.js: -------------------------------------------------------------------------------- 1 | const Big = require('bignumber.js') 2 | 3 | const DEFAULT_SIG_FIGS = 5 4 | const PRICE_SIG_FIGS = 5 5 | const AMOUNT_DECIMALS = 8 6 | 7 | /** 8 | * Smartly set the precision (decimal) on a value based off of the significant 9 | * digit maximum. For example, calling with 3.34 when the max sig figs allowed 10 | * is 5 would return '3.3400', the representation number of decimals IF they 11 | * weren't zeros. 12 | * 13 | * @param {number} number - number to manipulate 14 | * @param {number} [maxSigs] - default 5 15 | * @returns {string} str 16 | */ 17 | const setSigFig = (number = 0, maxSigs = DEFAULT_SIG_FIGS) => { 18 | const n = +(number) 19 | if (!isFinite(n)) { 20 | return number 21 | } 22 | const value = n.toPrecision(maxSigs) 23 | 24 | return /e/.test(value) 25 | ? new Big(value).toString() 26 | : value 27 | } 28 | 29 | const setPrecision = (number = 0, decimals = 0) => { 30 | const n = +(number) 31 | 32 | return (isFinite(n)) 33 | ? n.toFixed(decimals) 34 | : number 35 | } 36 | 37 | const prepareAmount = (amount = 0) => { 38 | return setPrecision(amount, AMOUNT_DECIMALS) 39 | } 40 | 41 | const preparePrice = (price = 0) => { 42 | return setSigFig(price, PRICE_SIG_FIGS) 43 | } 44 | 45 | module.exports = { 46 | setSigFig, setPrecision, prepareAmount, preparePrice 47 | } 48 | -------------------------------------------------------------------------------- /test/fixtures/response-ws2-server-order-book-R0.json: -------------------------------------------------------------------------------- 1 | [999,[[2567606289,1876.5,1.4305],[2567590859,1876.1,0.99670598],[2567602139,1876.1,1],[2567605297,1875.1,4.76123353],[2567605344,1875.1,0.01],[2567519477,1875,15],[2567552066,1875,0.01],[2567506309,1874.5,0.1],[2567603711,1874.4,1.820442],[2567477077,1874.2,0.19553],[2567477404,1874.2,0.07873206],[2567472587,1873.8,0.136159],[2567602057,1873.6,1.58],[2566800765,1873,1],[2567601717,1873,1.82726983],[2566876255,1872.6,0.0641877],[2567601259,1872.2,1.8],[2567048549,1871.7,0.01252505],[2567601253,1871.7,0.01252505],[2567606086,1871.7,1.50318748],[2566877752,1871.5,2.01],[2567606069,1871.5,4.8],[2567606105,1871,4.8],[2566874995,1870.9,2.0822],[2566863093,1870.7,0.1],[2567594844,1878.1,-0.42265059],[2567600874,1878.3,-0.1],[2567604766,1878.3,-1],[2567593507,1878.9,-1.662207],[2567598418,1879.5,-1.733],[2567588789,1879.6,-1.85096707],[2567593692,1880,-2],[2567564697,1880.1,-50],[2567564825,1880.2,-0.1],[2567587247,1880.3,-1.8071],[2567490789,1881,-1.59002156],[2567572064,1881,-5],[2567490713,1881.7,-1.79858828],[2567484859,1882.1,-0.1],[2567490678,1882.4,-2.001883],[2567601620,1882.9,-1.5],[2567484581,1883,-2],[2567546551,1883,-2],[2567606075,1883.4,-0.28613703],[2567603128,1883.5,-3.95],[2567474435,1883.7,-1.9],[2567504132,1883.7,-0.04646614],[2567504179,1883.7,-0.13263695],[2567403182,1884,-0.1],[2567442532,1884.5,-1.9324]]] 2 | -------------------------------------------------------------------------------- /examples/rest2/list_open_orders.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _capitalize = require('lodash/capitalize') 4 | const _isEmpty = require('lodash/isEmpty') 5 | const { prepareAmount, preparePrice } = require('bfx-api-node-util') 6 | const { RESTv2 } = require('../../index') 7 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 8 | 9 | async function execute () { 10 | const rest = new RESTv2({ 11 | apiKey, 12 | apiSecret, 13 | transform: true 14 | }) 15 | const filterByMarket = null 16 | 17 | debug('fetching open orders...') 18 | const allOrders = await rest.activeOrders() 19 | 20 | if (allOrders.length === 0) { 21 | return debug('no open orders matching filters') 22 | } 23 | 24 | const orders = _isEmpty(filterByMarket) 25 | ? allOrders 26 | : allOrders.filter(o => o.symbol === filterByMarket) 27 | 28 | debug('read %d open order(s)', orders.length) 29 | 30 | debugTable({ 31 | headers: [ 32 | 'Symbol', 'Type', 'Amount', 'Price', 'Status', 'ID', 'CID', 33 | 'Created', 'Updated' 34 | ], 35 | 36 | rows: orders.map((o) => [ 37 | o.symbol, o.type, prepareAmount(o.amount), preparePrice(o.price), 38 | _capitalize(o.status.split(':')[0]), o.id, o.cid, 39 | new Date(o.mtsCreate).toLocaleString(), 40 | new Date(o.mtsUpdate).toLocaleString() 41 | ]) 42 | }) 43 | } 44 | 45 | execute() 46 | -------------------------------------------------------------------------------- /examples/rest2/movements.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount, preparePrice } = require('bfx-api-node-util') 4 | const { RESTv2 } = require('../../index') 5 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 6 | 7 | const argFromCLI = require('../util/arg_from_cli') 8 | 9 | async function execute () { 10 | const rest = new RESTv2({ 11 | apiKey, 12 | apiSecret, 13 | transform: true 14 | }) 15 | const params = { 16 | ccy: argFromCLI(0, 'all') 17 | } 18 | const ccy = params.ccy === 'all' ? null : params.ccy 19 | 20 | debug('fetching movements for %s...', ccy || 'all currencies') 21 | 22 | const movements = await rest.movements(ccy) 23 | 24 | if (movements.length === 0) { 25 | return debug('no movements found') 26 | } 27 | 28 | debugTable({ 29 | headers: [ 30 | 'ID', 'Currency', 'Started', 'Updated', 'Status', 'Amount', 'Fees' 31 | ], 32 | 33 | rows: movements.map((m) => { 34 | const status = `${m.status[0].toUpperCase()}${m.status.substring(1).toLowerCase()}` 35 | const started = new Date(m.mtsStarted).toLocaleString() 36 | const updated = new Date(m.mtsUpdated).toLocaleString() 37 | 38 | return [ 39 | m.id, m.currency, started, updated, status, prepareAmount(m.amount), 40 | preparePrice(m.fees) 41 | ] 42 | }) 43 | }) 44 | } 45 | 46 | execute() 47 | -------------------------------------------------------------------------------- /examples/rest2/tickers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { preparePrice, prepareAmount } = require('bfx-api-node-util') 4 | const { RESTv2 } = require('../../index') 5 | const { debug, debugTable } = require('../util/setup') 6 | 7 | async function execute () { 8 | const rest = new RESTv2({ 9 | transform: true 10 | }) 11 | const filterByMarket = null 12 | 13 | debug('fetching symbol list...') 14 | 15 | const rawSymbols = await rest.symbols() 16 | 17 | debug('read %d symbols', rawSymbols.length) 18 | 19 | const symbols = rawSymbols 20 | .map(s => `t${s.toUpperCase()}`) 21 | .filter(s => ( 22 | !filterByMarket || (s === filterByMarket) 23 | )) 24 | 25 | if (symbols.length === 0) { 26 | return debug('no tickers match provided filters') 27 | } 28 | 29 | debug('fetching %d tickers...', symbols.length) 30 | 31 | const tickers = await rest.tickers(symbols) 32 | 33 | debugTable({ 34 | colWidths: [10, 14, 14, 14, 14, 14, 14, 18, 18], 35 | headers: [ 36 | 'Symbol', 'Last', 'High', 'Low', 'Daily Change', 'Bid', 'Ask', 'Bid Size', 37 | 'Ask Size' 38 | ], 39 | 40 | rows: tickers.map(t => ([ 41 | t.symbol, preparePrice(t.lastPrice), preparePrice(t.high), 42 | preparePrice(t.low), (t.dailyChange * 100).toFixed(2), preparePrice(t.bid), 43 | preparePrice(t.ask), prepareAmount(t.bidSize), prepareAmount(t.askSize) 44 | ])) 45 | }) 46 | } 47 | 48 | execute() 49 | -------------------------------------------------------------------------------- /examples/ws2/orders.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Order } = require('bfx-api-node-models') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | 7 | // Build new order 8 | const o = new Order({ 9 | cid: Date.now(), 10 | symbol: 'tBTCUSD', 11 | price: 589.10, 12 | amount: -0.02, 13 | type: Order.type.EXCHANGE_LIMIT 14 | }) 15 | 16 | async function execute () { 17 | const ws = new WSv2({ 18 | apiKey, 19 | apiSecret, 20 | transform: true 21 | }) 22 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 23 | await ws.open() 24 | await ws.auth() 25 | 26 | let orderClosed = false 27 | 28 | // Enable automatic updates 29 | o.registerListeners(ws) 30 | 31 | o.on('update', () => debug('updated: %s', o.toString())) 32 | o.on('close', () => { 33 | debug('order closed: %s', o.status) 34 | orderClosed = true 35 | }) 36 | 37 | debug('submitting order %d', o.cid) 38 | 39 | await o.submit() 40 | 41 | debug('got submit confirmation for order %d [%d]', o.cid, o.id) 42 | 43 | // wait a bit... 44 | await new Promise(resolve => setTimeout(resolve, 2 * 1000)) 45 | 46 | if (orderClosed) { 47 | return debug('order closed prematurely; did it auto-fill?') 48 | } 49 | 50 | debug('canceling...') 51 | await o.cancel() 52 | debug('got cancel confirmation for order %d', o.cid) 53 | await ws.close() 54 | } 55 | 56 | execute() 57 | -------------------------------------------------------------------------------- /examples/rest2/claim_positions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { RESTv2 } = require('../../index') 4 | const _isEmpty = require('lodash/isEmpty') 5 | const { args: { apiKey, apiSecret }, debug, debugTable, readline } = require('../util/setup') 6 | 7 | async function execute () { 8 | const rest = new RESTv2({ 9 | apiKey, 10 | apiSecret, 11 | transform: true 12 | }) 13 | const filterByMarket = false 14 | const allPositions = await rest.positions() 15 | const positions = _isEmpty(filterByMarket) 16 | ? allPositions 17 | : allPositions.filter(({ symbol }) => symbol === filterByMarket) 18 | 19 | if (positions.length === 0) { 20 | debug('no positions match filter') 21 | return 22 | } 23 | 24 | debug( 25 | 'found %d open positions on market(s) %s\n', positions.length, 26 | positions.map(({ symbol }) => symbol).join(',') 27 | ) 28 | 29 | debugTable({ 30 | headers: ['Symbol', 'Status', 'Amount', 'Base Price', 'P/L'], 31 | rows: positions.map(p => ([ 32 | p.symbol, p.status, p.amount, p.basePrice, p.pl 33 | ])) 34 | }) 35 | 36 | const confirm = await readline.questionAsync( 37 | '> Are you sure you want to claim the position(s) listed above? ' 38 | ) 39 | 40 | if (confirm.toLowerCase()[0] !== 'y') { 41 | return 42 | } 43 | 44 | debug('') 45 | debug('claiming positions...') 46 | 47 | await Promise.all(positions.map(p => p.claim(rest))) 48 | 49 | debug('done!') 50 | readline.close() 51 | } 52 | 53 | execute() 54 | -------------------------------------------------------------------------------- /examples/ws2/oc_multi.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Order } = require('bfx-api-node-models') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | 7 | const oA = new Order({ 8 | symbol: 'tBTCUSD', 9 | price: 200, 10 | amount: 1, 11 | type: 'EXCHANGE LIMIT' 12 | }) 13 | 14 | const oB = new Order({ 15 | symbol: 'tETHUSD', 16 | price: 50, 17 | amount: 1, 18 | type: 'EXCHANGE LIMIT' 19 | }) 20 | 21 | async function execute () { 22 | const ws = new WSv2({ 23 | apiKey, 24 | apiSecret, 25 | transform: true 26 | }) 27 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 28 | await ws.open() 29 | await ws.auth() 30 | 31 | oA.registerListeners(ws) 32 | oB.registerListeners(ws) 33 | 34 | await oA.submit() 35 | debug('created order A') 36 | 37 | await oB.submit() 38 | debug('created order B') 39 | 40 | let oAClosed = false 41 | let oBClosed = false 42 | 43 | oA.on('close', async () => { 44 | debug('order A cancelled: %s', oA.status) 45 | 46 | oAClosed = true 47 | if (oBClosed) return ws.close() 48 | }) 49 | 50 | oB.on('close', async () => { 51 | debug('order B cancelled: %s', oB.status) 52 | 53 | oBClosed = true 54 | if (oAClosed) return ws.close() 55 | }) 56 | 57 | ws.send([0, 'oc_multi', null, { 58 | id: [oA.id, oB.id] 59 | }]) 60 | 61 | debug('sent oc_multi for orders A & B') 62 | 63 | await ws.close() 64 | } 65 | 66 | execute() 67 | -------------------------------------------------------------------------------- /examples/ws2/oco-order.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Order } = require('bfx-api-node-models') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | 7 | // Build new order 8 | const o = new Order({ 9 | cid: Date.now(), 10 | symbol: 'tBTCUSD', 11 | type: Order.type.EXCHANGE_LIMIT, 12 | amount: -0.05, 13 | 14 | oco: true, 15 | price: 2000, 16 | priceAuxLimit: 1000 17 | }) 18 | 19 | async function execute () { 20 | const ws = new WSv2({ 21 | apiKey, 22 | apiSecret, 23 | transform: true 24 | }) 25 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 26 | await ws.open() 27 | await ws.auth() 28 | 29 | o.registerListeners(ws) // enable automatic updates 30 | 31 | let orderClosed = false 32 | 33 | o.on('update', () => { 34 | debug('updated: %s', o.toString()) 35 | }) 36 | 37 | o.on('close', () => { 38 | debug('order closed: %s', o.status) 39 | orderClosed = true 40 | }) 41 | 42 | debug('submitting order %d', o.cid) 43 | await o.submit() 44 | debug('got submit confirmation for order %d [%d]', o.cid, o.id) 45 | 46 | // wait a bit... 47 | await new Promise(resolve => setTimeout(resolve, 2 * 1000)) 48 | 49 | if (orderClosed) { 50 | return debug('order closed prematurely; did it auto-fill?') 51 | } 52 | 53 | debug('canceling...') 54 | 55 | await o.cancel() 56 | debug('got cancel confirmation for order %d', o.cid) 57 | await ws.close() 58 | } 59 | 60 | execute() 61 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | verbose: false 2 | instrumentation: 3 | root: . 4 | extensions: 5 | - .js 6 | default-excludes: true 7 | excludes: [] 8 | embed-source: false 9 | variable: __coverage__ 10 | compact: true 11 | preserve-comments: false 12 | complete-copy: false 13 | save-baseline: false 14 | baseline-file: ./coverage/coverage-baseline.json 15 | include-all-sources: false 16 | include-pid: false 17 | reporting: 18 | print: summary 19 | reports: 20 | - lcov 21 | dir: ./source/images/coverage 22 | watermarks: 23 | statements: [50, 80] 24 | lines: [50, 80] 25 | functions: [50, 80] 26 | branches: [50, 80] 27 | report-config: 28 | clover: {file: clover.xml} 29 | cobertura: {file: cobertura-coverage.xml} 30 | json: {file: coverage-final.json} 31 | json-summary: {file: coverage-summary.json} 32 | lcovonly: {file: lcov.info} 33 | teamcity: {file: null, blockName: Code Coverage Summary} 34 | text: {file: null, maxCols: 0} 35 | text-lcov: {file: lcov.info} 36 | text-summary: {file: null} 37 | hooks: 38 | hook-run-in-context: false 39 | post-require-hook: null 40 | handle-sigint: false 41 | check: 42 | global: 43 | statements: 0 44 | lines: 0 45 | branches: 0 46 | functions: 0 47 | excludes: [] 48 | each: 49 | statements: 0 50 | lines: 0 51 | branches: 0 52 | functions: 0 53 | excludes: [] 54 | -------------------------------------------------------------------------------- /examples/rest2/order-history.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { prepareAmount, preparePrice } = require('bfx-api-node-util') 4 | const _isEmpty = require('lodash/isEmpty') 5 | const { RESTv2 } = require('../../index') 6 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 7 | 8 | const START = Date.now() - (30 * 24 * 60 * 60 * 1000 * 1000) 9 | const END = Date.now() 10 | const LIMIT = 25 11 | 12 | async function execute () { 13 | const rest = new RESTv2({ 14 | apiKey, 15 | apiSecret, 16 | transform: true 17 | }) 18 | const market = 'tBTCUSD' 19 | 20 | if (_isEmpty(market)) { 21 | return debug('market required') 22 | } 23 | 24 | debug('fetching 30d order history for %s...', market) 25 | 26 | const orders = await rest.orderHistory(market, START, END, LIMIT) 27 | 28 | if (orders.length === 0) { 29 | return debug('no historical orders for %s', market) 30 | } 31 | 32 | debugTable({ 33 | headers: [ 34 | 'Order ID', 'Created', 'Updated', 'Amount', 'Filled', 'Price', 'Status' 35 | ], 36 | 37 | rows: orders.map(o => { 38 | o.status = `${o.status[0].toUpperCase()}${o.status.substring(1)}` 39 | o.mtsCreate = new Date(o.mtsCreate).toLocaleString() 40 | o.mtsUpdate = new Date(o.mtsUpdate).toLocaleString() 41 | 42 | return [ 43 | o.id, o.mtsCreate, o.mtsUpdate, prepareAmount(o.amountOrig), 44 | prepareAmount(o.amountOrig - o.amount), 45 | preparePrice(o.price), o.status.split(':')[0] 46 | ] 47 | }) 48 | }) 49 | } 50 | 51 | execute() 52 | -------------------------------------------------------------------------------- /examples/ws2/sequencing.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _isArray = require('lodash/isArray') 4 | const _isFinite = require('lodash/isFinite') 5 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 6 | const WSv2 = require('../../lib/transports/ws2') 7 | 8 | async function execute () { 9 | const ws = new WSv2({ 10 | apiKey, 11 | apiSecret, 12 | transform: true 13 | }) 14 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 15 | await ws.open() 16 | await ws.auth() 17 | 18 | // Enables internal sequence tracking; an error will be emitted if there is a 19 | // seq # mis-match 20 | await ws.enableSequencing({ audit: true }) 21 | 22 | if (!ws.isFlagEnabled(65536)) { 23 | throw new Error('seq enable succeeded, but flag not updated') 24 | } 25 | 26 | debug('sequencing enabled') 27 | 28 | await ws.subscribeTrades('tBTCUSD') 29 | 30 | ws.on('message', (msg) => { 31 | if (!_isArray(msg)) return // only array messages have sequence #s 32 | 33 | // auth seq number, available as the last element on chan 0 packets 34 | const authSeq = msg[0] === 0 && msg[1] !== 'hb' 35 | ? msg[msg.length - 1] 36 | : NaN 37 | 38 | // public seq number, last or 2nd to last element on all packets 39 | const seq = msg[0] === 0 && msg[1] !== 'hb' 40 | ? msg[msg.length - 2] 41 | : msg[msg.length - 1] 42 | 43 | if (!_isFinite(authSeq)) { 44 | debug('recv public seq # %d', seq) 45 | } else { 46 | debug('recv public seq # %d, auth seq # %d', seq, authSeq) 47 | } 48 | }) 49 | await ws.close() 50 | } 51 | 52 | execute() 53 | -------------------------------------------------------------------------------- /examples/ws2/cancel_all.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _isEmpty = require('lodash/isEmpty') 4 | const { args: { apiKey, apiSecret }, debug, readline } = require('../util/setup') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | 7 | async function execute () { 8 | const ws = new WSv2({ 9 | apiKey, 10 | apiSecret, 11 | transform: true 12 | }) 13 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 14 | await ws.open() 15 | 16 | const filterByMarket = null 17 | 18 | debug('awaiting order snapshot...') 19 | 20 | const allOrders = await new Promise((resolve) => { 21 | ws.onOrderSnapshot({}, resolve) 22 | return ws.auth() 23 | }) 24 | 25 | if (allOrders.length === 0) { 26 | debug('no orders to cancel') 27 | await ws.close() 28 | readline.close() 29 | return 30 | } 31 | 32 | const orders = _isEmpty(filterByMarket) 33 | ? allOrders 34 | : allOrders.filter(o => o.symbol === filterByMarket) 35 | 36 | debug('received snapshot (%d orders)', orders.length) 37 | debug('') 38 | orders.forEach(o => debug('%s', o.toString())) 39 | debug('') 40 | 41 | const confirm = await readline.questionAsync( 42 | '> Are you sure you want to close the orders(s) listed above? ' 43 | ) 44 | 45 | if (confirm.toLowerCase()[0] !== 'y') { 46 | return 47 | } 48 | 49 | debug('') 50 | debug('cancelling all..') 51 | 52 | const confirmations = await ws.cancelOrders(orders) 53 | 54 | debug( 55 | 'done! cancelled the following order IDs: %s', 56 | confirmations.map(o => o[0]).join(', ') 57 | ) 58 | await ws.close() 59 | readline.close() 60 | } 61 | 62 | execute() 63 | -------------------------------------------------------------------------------- /examples/ws2/order_book_viz.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const blessed = require('blessed') 4 | const blessedContrib = require('blessed-contrib') 5 | const _isEmpty = require('lodash/isEmpty') 6 | const _reverse = require('lodash/reverse') 7 | const { preparePrice, prepareAmount } = require('bfx-api-node-util') 8 | const { debug } = require('../util/setup') 9 | const WSv2 = require('../../lib/transports/ws2') 10 | 11 | async function execute () { 12 | const ws = new WSv2({ 13 | transform: true, 14 | manageOrderBooks: true // tell the ws client to maintain full sorted OBs 15 | }) 16 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 17 | await ws.open() 18 | 19 | const market = 'tBTCUSD' 20 | 21 | if (_isEmpty(market)) { 22 | throw new Error('market required') 23 | } 24 | 25 | const screen = blessed.screen() 26 | const bookTable = blessedContrib.table({ 27 | fg: 'white', 28 | label: `Order Book ${market}`, 29 | border: { 30 | type: 'line', 31 | fg: 'green' 32 | }, 33 | 34 | columnSpacing: 5, 35 | columnWidth: [10, 20, 10] 36 | }) 37 | 38 | screen.append(bookTable) 39 | 40 | ws.onOrderBook({ symbol: market }, (ob) => { 41 | const data = [] 42 | 43 | _reverse(ob.asks).forEach(row => data.push([ 44 | preparePrice(row[0]), prepareAmount(row[2]), row[1] 45 | ])) 46 | 47 | ob.bids.forEach(row => data.push([ 48 | preparePrice(row[0]), prepareAmount(row[2]), row[1] 49 | ])) 50 | 51 | bookTable.setData({ 52 | headers: ['Price', 'Amount', 'Count'], 53 | data 54 | }) 55 | 56 | screen.render() 57 | }) 58 | 59 | await ws.subscribeOrderBook(market, 'P0', '25') 60 | } 61 | 62 | execute() 63 | -------------------------------------------------------------------------------- /examples/rest2/positions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _capitalize = require('lodash/capitalize') 4 | const _map = require('lodash/map') 5 | const { prepareAmount, preparePrice } = require('bfx-api-node-util') 6 | const { RESTv2 } = require('../../index') 7 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 8 | 9 | async function execute () { 10 | const rest = new RESTv2({ 11 | apiKey, 12 | apiSecret, 13 | transform: true 14 | }) 15 | debug('fetching positions...') 16 | 17 | const positions = await rest.positions() 18 | const symbols = _map(positions, 'symbol') 19 | 20 | if (positions.length === 0) { 21 | return debug('no open positions') 22 | } 23 | 24 | debug('found %d open positions', positions.length) 25 | debug('fetching tickers for: %s', symbols.join(', ')) 26 | 27 | const prices = {} 28 | const rawTickers = await rest.tickers(symbols) 29 | 30 | rawTickers.forEach(({ symbol, lastPrice }) => (prices[symbol] = +lastPrice)) 31 | 32 | debugTable({ 33 | headers: [ 34 | 'ID', 'Symbol', 'Status', 'Amount', 'Base Price', 'Funding Cost', 35 | 'Base Value', 'Net Value', 'P/L', 'P/L %' 36 | ], 37 | 38 | rows: positions.map((p) => { 39 | const nv = +prices[p.symbol] * +p.amount 40 | const pl = nv - (+p.basePrice * +p.amount) 41 | const plPerc = (pl / nv) * 100.0 42 | 43 | return [ 44 | p.id, p.symbol, _capitalize(p.status), prepareAmount(p.amount), 45 | preparePrice(p.basePrice), prepareAmount(p.marginFunding), 46 | prepareAmount(+p.marginFunding + (+p.amount * +p.basePrice)), 47 | prepareAmount(nv), prepareAmount(pl), plPerc.toFixed(2) 48 | ] 49 | }) 50 | }) 51 | } 52 | 53 | execute() 54 | -------------------------------------------------------------------------------- /docs/scripts/collapse.js: -------------------------------------------------------------------------------- 1 | function hideAllButCurrent(){ 2 | //by default all submenut items are hidden 3 | //but we need to rehide them for search 4 | document.querySelectorAll("nav > ul").forEach(function(parent) { 5 | if (parent.className.indexOf("collapse_top") !== -1) { 6 | parent.style.display = "none"; 7 | } 8 | }); 9 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(parent) { 10 | parent.style.display = "none"; 11 | }); 12 | document.querySelectorAll("nav > h3").forEach(function(section) { 13 | if (section.className.indexOf("collapsed_header") !== -1) { 14 | section.addEventListener("click", function(){ 15 | if (section.nextSibling.style.display === "none") { 16 | section.nextSibling.style.display = "block"; 17 | } else { 18 | section.nextSibling.style.display = "none"; 19 | } 20 | }); 21 | } 22 | }); 23 | 24 | //only current page (if it exists) should be opened 25 | var file = window.location.pathname.split("/").pop().replace(/\.html/, ''); 26 | document.querySelectorAll("nav > ul > li > a").forEach(function(parent) { 27 | var href = parent.attributes.href.value.replace(/\.html/, ''); 28 | if (file === href) { 29 | if (parent.parentNode.parentNode.className.indexOf("collapse_top") !== -1) { 30 | parent.parentNode.parentNode.style.display = "block"; 31 | } 32 | parent.parentNode.querySelectorAll("ul li").forEach(function(elem) { 33 | elem.style.display = "block"; 34 | }); 35 | } 36 | }); 37 | } 38 | 39 | hideAllButCurrent(); -------------------------------------------------------------------------------- /test/fixtures/response-ws-1-orderbook-R0.json: -------------------------------------------------------------------------------- 1 | [ 13242, 2 | [ [ 2578842316, 1967.5, 0.1 ], 3 | [ 2578789721, 1967, 2 ], 4 | [ 2578787950, 1966.7, 0.1 ], 5 | [ 2578841323, 1966.7, 1.48 ], 6 | [ 2578790926, 1966.5, 0.1 ], 7 | [ 2578787536, 1966.3, 2.13425 ], 8 | [ 2578782502, 1965.6, 2 ], 9 | [ 2578824150, 1965.5, 0.1 ], 10 | [ 2578754476, 1964.9, 0.1 ], 11 | [ 2578782291, 1964.8, 2 ], 12 | [ 2578781657, 1964.1, 2 ], 13 | [ 2578781578, 1963.4, 2 ], 14 | [ 2578758200, 1962.7, 0.1 ], 15 | [ 2578759464, 1962.7, 2.2418413 ], 16 | [ 2578757543, 1962, 2.24436613 ], 17 | [ 2578842121, 1962, 0.43104651 ], 18 | [ 2578838572, 1961.5, 2 ], 19 | [ 2578757448, 1961.4, 2 ], 20 | [ 2578757373, 1960.7, 2.0164 ], 21 | [ 2578758228, 1960.7, 0.1 ], 22 | [ 2578764149, 1960.2, 3 ], 23 | [ 2578841514, 1960.2, 0.25 ], 24 | [ 2578754697, 1959.9, 0.1 ], 25 | [ 2578757246, 1959.9, 2.1 ], 26 | [ 2578753035, 1959.3, 2.3016 ], 27 | [ 2578842390, 1968.8, -0.07953231 ], 28 | [ 2578842404, 1968.8, -0.1 ], 29 | [ 2578842259, 1968.9, -0.8787 ], 30 | [ 2578840418, 1969, -11.67581916 ], 31 | [ 2578842168, 1969.7, -0.01 ], 32 | [ 2578840772, 1969.8, -10 ], 33 | [ 2578833728, 1969.9, -18.60507973 ], 34 | [ 2578760844, 1970, -0.07251047 ], 35 | [ 2578814935, 1970, -0.07606679 ], 36 | [ 2578828061, 1970, -2 ], 37 | [ 2578734028, 1970.7, -0.1 ], 38 | [ 2578842236, 1970.9, -2.08080841 ], 39 | [ 2578841867, 1971.5, -2.31 ], 40 | [ 2578841774, 1972.3, -2.09 ], 41 | [ 2578732893, 1972.7, -0.1 ], 42 | [ 2578841607, 1972.9, -2.2 ], 43 | [ 2578840675, 1973.6, -2.06821658 ], 44 | [ 2578842101, 1973.9, -0.58625479 ], 45 | [ 2578760447, 1974, -1 ], 46 | [ 2578763766, 1974, -1 ], 47 | [ 2578840619, 1974.3, -2.147368 ], 48 | [ 2578798094, 1974.6, -0.01 ], 49 | [ 2578731148, 1974.7, -0.1 ], 50 | [ 2578734894, 1974.8, -49.9 ], 51 | [ 2578420769, 1975, -0.02 ] ] ] 52 | -------------------------------------------------------------------------------- /examples/ws2/ox_multi.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Order } = require('bfx-api-node-models') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | 7 | const oA = new Order({ 8 | symbol: 'tBTCUSD', 9 | price: 200, 10 | amount: 1, 11 | type: 'EXCHANGE LIMIT' 12 | }) 13 | 14 | const oB = new Order({ 15 | symbol: 'tETHUSD', 16 | price: 50, 17 | amount: 1, 18 | type: 'EXCHANGE LIMIT' 19 | }) 20 | 21 | const oC = new Order({ 22 | symbol: 'tETHBTC', 23 | price: 1, 24 | amount: 1, 25 | type: 'EXCHANGE LIMIT' 26 | }) 27 | 28 | async function execute () { 29 | const ws = new WSv2({ 30 | apiKey, 31 | apiSecret, 32 | transform: true 33 | }) 34 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 35 | await ws.open() 36 | await ws.auth() 37 | 38 | oA.registerListeners(ws) 39 | oB.registerListeners(ws) 40 | oC.registerListeners(ws) 41 | 42 | let oAClosed = false 43 | let oBClosed = false 44 | let oCClosed = false 45 | 46 | oA.on('close', async () => { 47 | debug('order A cancelled: %s', oA.status) 48 | 49 | oAClosed = true 50 | if (oBClosed && oCClosed) return ws.close() 51 | }) 52 | 53 | oB.on('close', async () => { 54 | debug('order B cancelled: %s', oB.status) 55 | 56 | oBClosed = true 57 | if (oAClosed && oCClosed) return ws.close() 58 | }) 59 | 60 | oC.on('close', async () => { 61 | debug('order C cancelled: %s', oC.status) 62 | 63 | oCClosed = true 64 | if (oAClosed && oBClosed) return ws.close() 65 | }) 66 | 67 | await oA.submit() 68 | debug('created order A') 69 | 70 | await oB.submit() 71 | debug('created order B') 72 | 73 | await oC.submit() 74 | debug('created order C') 75 | 76 | ws.submitOrderMultiOp([ 77 | ['oc', { id: oA.id }], 78 | ['oc_multi', { id: [oB.id, oC.id] }] 79 | ]) 80 | 81 | debug('sent ox_multi to cancel order A and orders [B, C]') 82 | } 83 | 84 | execute() 85 | -------------------------------------------------------------------------------- /examples/ws2/atomic_order_update.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Order } = require('bfx-api-node-models') 4 | const { args: { apiKey, apiSecret }, debug } = require('../util/setup') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | 7 | const SYMBOL = 'tBTCUSD' 8 | 9 | async function execute () { 10 | const ws = new WSv2({ 11 | apiKey, 12 | apiSecret, 13 | transform: true, 14 | manageOrderBooks: true, 15 | 16 | packetWDDelay: 10 * 1000, 17 | autoReconnect: true, 18 | seqAudit: true 19 | }) 20 | ws.on('error', e => debug('WSv2 error: %s', e.message | e)) 21 | await ws.open() 22 | await ws.auth() 23 | 24 | const orderSent = false 25 | 26 | await ws.subscribeOrderBook(SYMBOL, 'P0', '25') 27 | debug('subscribed to order book %s:P0:25', SYMBOL) 28 | 29 | ws.onOrderBook({ symbol: SYMBOL }, async (ob) => { 30 | const topBidL = ob.topBidLevel() 31 | 32 | if (topBidL === null || orderSent) { 33 | return 34 | } 35 | 36 | debug('taking out price level: %j', topBidL) 37 | 38 | const o = new Order({ 39 | symbol: SYMBOL, 40 | type: Order.type.EXCHANGE_LIMIT, 41 | price: topBidL[0], 42 | amount: topBidL[2] * -1.1 // sell through top bid 43 | }, ws) 44 | 45 | debug('submitting: %s', o.toString()) 46 | 47 | o.registerListeners() 48 | await o.submit() 49 | 50 | debug('order submitted') 51 | 52 | o.once('update', async (o) => { 53 | debug('got order update: %s', o.status) 54 | 55 | if (!o.isPartiallyFilled()) { 56 | return 57 | } 58 | 59 | debug('order is partially filled, amount %f', o.amount) 60 | debug('increasing amount w/ delta %f', o.amount * 2) 61 | 62 | await o.update({ delta: `${o.amount * 2}` }) 63 | debug('order updated, new amount %f', o.amount) 64 | debug('setting price to %f', o.price * 1.05) 65 | 66 | await o.update({ price: `${o.price * 1.05}` }) 67 | debug('order updated, new price %f', o.price) 68 | }) 69 | }) 70 | await ws.close() 71 | } 72 | 73 | execute() 74 | -------------------------------------------------------------------------------- /examples/ws2_manager.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.DEBUG = 'bfx:examples:*' 4 | 5 | const _flatten = require('lodash/flatten') 6 | const { RESTv2 } = require('../../index') 7 | const { args, debug } = require('./util/setup') 8 | const Manager = require('../lib/ws2_manager') 9 | 10 | const { apiKey, apiSecret } = args 11 | 12 | async function execute () { 13 | const rest = new RESTv2({ 14 | apiKey, 15 | apiSecret, 16 | transform: true 17 | }) 18 | 19 | debug('fetching symbol details...') 20 | 21 | const details = await rest.symbolDetails() 22 | const symbols = details.map(d => `t${d.pair.toUpperCase()}`) 23 | const timeFrames = ['1m', '5m', '30m', '1h', '6h'] 24 | const keys = _flatten(symbols.map(s => { 25 | return timeFrames.map(tf => `trade:${tf}:${s}`) 26 | })) 27 | 28 | const m = new Manager({ ...args }) 29 | 30 | m.on('error', (err) => { 31 | debug('error: %s', err) 32 | }) 33 | 34 | m.once('open', () => { 35 | debug('open') 36 | 37 | keys.forEach(key => { 38 | m.subscribeCandles(key) 39 | m.onCandle({ key }, (candles) => { 40 | debug('recv %d candles on channel %s', candles.length, key) 41 | }) 42 | }) 43 | 44 | symbols.forEach(symbol => { 45 | m.subscribeTrades(symbol) 46 | m.onTrades({ symbol }, (trades) => { 47 | debug('recv %d trades on channel %s', trades.length, symbol) 48 | }) 49 | }) 50 | 51 | symbols.forEach(symbol => { 52 | m.subscribeTicker(symbol) 53 | m.onTicker({ symbol }, (ticker) => { 54 | debug('recv ticker on channel %s: %j', symbol, ticker) 55 | }) 56 | }) 57 | 58 | symbols.forEach(symbol => { 59 | m.subscribeOrderBook(symbol) 60 | m.onOrderBook({ symbol }, (update) => { 61 | debug('recv book update on channel %s: %j', symbol, update) 62 | }) 63 | }) 64 | 65 | setInterval(() => { 66 | debug('num keys: %d', keys.length) 67 | debug('num sockets: %d', m.getNumSockets()) 68 | debug('socket info: %j', m.getSocketInfo()) 69 | }, 5000) 70 | }) 71 | 72 | m.openSocket() 73 | } 74 | 75 | execute() 76 | -------------------------------------------------------------------------------- /examples/rest2/close_positions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const PI = require('p-iteration') 4 | const _isEmpty = require('lodash/isEmpty') 5 | const WSv2 = require('../../lib/transports/ws2') 6 | const { RESTv2 } = require('../../index') 7 | const { args: { apiKey, apiSecret }, debug, debugTable, readline } = require('../util/setup') 8 | 9 | async function execute () { 10 | const ws = new WSv2({ 11 | apiKey, 12 | apiSecret, 13 | transform: true 14 | }) 15 | const rest = new RESTv2({ 16 | apiKey, 17 | apiSecret, 18 | transform: true 19 | }) 20 | const filterByMarket = null 21 | const allPositions = await rest.positions() 22 | await ws.open() 23 | await ws.auth() 24 | 25 | if (allPositions.length === 0) { 26 | debug('no open positions') 27 | await ws.close() 28 | readline.close() 29 | return 30 | } 31 | 32 | debug( 33 | 'found %d open positions on market(s) %s', allPositions.length, 34 | allPositions.map(({ symbol }) => symbol).join(',') 35 | ) 36 | 37 | const positions = _isEmpty(filterByMarket) 38 | ? allPositions 39 | : allPositions.filter(({ symbol }) => symbol === filterByMarket) 40 | 41 | if (positions.length === 0) { 42 | debug('no positions match filter') 43 | await ws.close() 44 | readline.close() 45 | return 46 | } 47 | 48 | const orders = positions.map(p => p.orderToClose(ws)) 49 | 50 | debugTable({ 51 | headers: [ 52 | 'ID', 'Symbol', 'Status', 'Amount', 'Base Price', 'P/L' 53 | ], 54 | 55 | rows: positions.map(p => ([ 56 | p.id, p.symbol, p.status, p.amount, p.basePrice, p.pl 57 | ])) 58 | }) 59 | 60 | orders.forEach(o => (debug('%s', o.toString()))) 61 | debug('') 62 | 63 | const confirm = await readline.questionAsync( 64 | '> Are you sure you want to submit the order(s) listed above? ' 65 | ) 66 | 67 | if (confirm.toLowerCase()[0] !== 'y') { 68 | await ws.close() 69 | readline.close() 70 | return 71 | } 72 | 73 | debug('') 74 | 75 | ws.onOrderClose({}, ({ id, symbol, status }) => { 76 | debug('received confirmation of order %d closed on %s: %s', id, symbol, status) 77 | }) 78 | 79 | await PI.forEachSeries(orders, o => o.submit()) 80 | 81 | debug('') 82 | debug('closed %d positions', positions.length) 83 | 84 | await ws.close() 85 | readline.close() 86 | } 87 | 88 | execute() 89 | -------------------------------------------------------------------------------- /examples/rest2/submit_order.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { Order } = require('bfx-api-node-models') 4 | const { RESTv2 } = require('../../index') 5 | const { args: { apiKey, apiSecret }, debug, readline } = require('../util/setup') 6 | 7 | const UPDATE_DELAY_MS = 5 * 1000 8 | const CANCEL_DELAY_MS = 10 * 1000 9 | 10 | async function execute () { 11 | const rest = new RESTv2({ 12 | apiKey, 13 | apiSecret 14 | }) 15 | const { 16 | symbol, price, amount, type, affiliateCode, onlySubmitOrder, skipConfirm, 17 | priceStop, distance 18 | } = { 19 | // needed in order to pipe data to the process, until we can figure out a 20 | // workaround 21 | skipConfirm: false, 22 | onlySubmitOrder: false, // allows this script to be used only for submits 23 | symbol: 'tLEOUSD', 24 | price: 2, 25 | amount: -6, 26 | type: Order.type.LIMIT, 27 | affiliateCode: 'xZvWHMNR' 28 | } 29 | 30 | const o = new Order({ 31 | cid: Date.now(), 32 | symbol, 33 | price, 34 | priceAuxLimit: priceStop, 35 | priceTrailing: distance, 36 | amount, 37 | type, 38 | affiliateCode 39 | }, rest) 40 | 41 | if (!skipConfirm) { 42 | const confirm = await readline.questionAsync([ 43 | '> Are you sure you want to submit this order?', 44 | `> ${o.toString()}`, 45 | '> ' 46 | ].join('\n')) 47 | 48 | if (confirm.toLowerCase()[0] !== 'y') { 49 | readline.close() 50 | return 51 | } 52 | } 53 | 54 | debug('submitting order: %s', o.toString()) 55 | 56 | await o.submit() 57 | 58 | debug('order successfully submitted! (id %j, cid %j, gid %j)', o.id, o.cid, o.gid) 59 | 60 | if (onlySubmitOrder) { 61 | readline.close() 62 | return // for bfx-cli 63 | } 64 | 65 | debug('') 66 | debug('will update price to $3.00 in %fs...', UPDATE_DELAY_MS / 1000) 67 | 68 | await new Promise(resolve => setTimeout(resolve, UPDATE_DELAY_MS)) 69 | 70 | debug('') 71 | debug('updating order price...') 72 | 73 | const updateNotification = await o.update({ price: 3 }) 74 | debug('successfully updated! (%s: %s)', updateNotification.status, updateNotification.text) 75 | debug('') 76 | debug('will cancel the order in %fs', CANCEL_DELAY_MS / 1000) 77 | 78 | await new Promise(resolve => setTimeout(resolve, CANCEL_DELAY_MS)) 79 | 80 | debug('') 81 | debug('cancelling order...') 82 | 83 | const cancelNotification = await o.cancel() 84 | debug('successfully canceled! (%s: %s)', cancelNotification.status, cancelNotification.text) 85 | readline.close() 86 | } 87 | 88 | execute() 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitfinex-api-node", 3 | "version": "8.0.0", 4 | "description": "Node reference library for Bitfinex API", 5 | "engines": { 6 | "node": ">=18.0" 7 | }, 8 | "main": "index.js", 9 | "husky": { 10 | "hooks": { 11 | "pre-commit": "npm test" 12 | } 13 | }, 14 | "scripts": { 15 | "lint": "standard", 16 | "lint:fix": "standard --fix", 17 | "test": "npm run lint && npm run unit", 18 | "unit": "NODE_ENV=test mocha -b --recursive", 19 | "docs": "rm -rf docs && node_modules/.bin/jsdoc --configure .jsdoc.json --verbose", 20 | "prepare": "husky install" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/bitfinexcom/bitfinex-api-node.git" 25 | }, 26 | "keywords": [ 27 | "bitfinex", 28 | "bitcoin", 29 | "BTC" 30 | ], 31 | "contributors": [ 32 | "Ezequiel Wernicke (https://www.bitfinex.com)", 33 | "Josh Rossi (https://www.bitfinex.com)", 34 | "Cris Mihalache (https://www.bitfinex.com)", 35 | "Robert Kowalski (https://www.bitfinex.com)", 36 | "Simone Poggi (https://www.bitfinex.com)", 37 | "Paolo Ardoino (https://www.bitfinex.com)", 38 | "Abhishek Shrestha (https://www.bitfinex.com)" 39 | ], 40 | "license": "MIT", 41 | "bugs": { 42 | "url": "https://github.com/bitfinexcom/bitfinex-api-node/issues" 43 | }, 44 | "homepage": "http://bitfinexcom.github.io/bitfinex-api-node/", 45 | "devDependencies": { 46 | "bfx-api-mock-srv": "^2.0.0", 47 | "blessed": "0.1.81", 48 | "blessed-contrib": "^1.0.11", 49 | "cli-table3": "^0.6.5", 50 | "docdash": "^2.0.2", 51 | "dotenv": "^16.4.5", 52 | "husky": "^9.1.6", 53 | "jsdoc-to-markdown": "^9.0.1", 54 | "mocha": "^10.7.3", 55 | "p-iteration": "1.1.8", 56 | "readline-promise": "1.0.4", 57 | "socks-proxy-agent": "^8.0.4", 58 | "standard": "^17.1.2" 59 | }, 60 | "dependencies": { 61 | "bfx-api-node-models": "^2.0.0", 62 | "bfx-api-node-rest": "^7.0.0", 63 | "bfx-api-node-util": "^1.0.12", 64 | "bfx-api-node-ws1": "^1.0.0", 65 | "bignumber.js": "9.0.0", 66 | "cbq": "0.0.1", 67 | "debug": "4.3.3", 68 | "lodash": "^4.17.4", 69 | "lodash.throttle": "4.1.1", 70 | "lossless-json": "1.0.3", 71 | "promise-throttle": "1.0.1", 72 | "ws": "7.5.10" 73 | }, 74 | "standard": { 75 | "ignore": [ 76 | "/docs/**/*.js" 77 | ] 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/examples/args_from_env.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const assert = require('assert') 5 | const _isUndefined = require('lodash/isUndefined') 6 | const _isObject = require('lodash/isObject') 7 | const _isString = require('lodash/isString') 8 | const _isEmpty = require('lodash/isEmpty') 9 | const { SocksProxyAgent } = require('socks-proxy-agent') 10 | const argsFromEnv = require('../../examples/util/args_from_env') 11 | 12 | describe('argsFromEnv', () => { 13 | it('pulls api credentials from the environment only if available', () => { 14 | delete process.env.API_KEY 15 | delete process.env.API_SECRET 16 | 17 | let args = argsFromEnv() 18 | 19 | assert.ok(_isObject(args), 'did not return an object') 20 | assert(_isUndefined(args.apiKey), 'api key parsed although not present on env') 21 | assert(_isUndefined(args.apiSecret), 'api secret parsed although not present on env') 22 | 23 | process.env.API_KEY = '42' 24 | process.env.API_SECRET = '9000' 25 | 26 | args = argsFromEnv() 27 | 28 | assert.ok(_isObject(args), 'did not return an object') 29 | assert.strictEqual(args.apiKey, '42', 'api key not pulled from env') 30 | assert.strictEqual(args.apiSecret, '9000', 'api secret not pull from env') 31 | }) 32 | 33 | it('provides a connection agent if a socks proxy url is available on the env', () => { 34 | const url = 'socks4://localhost:9998' 35 | delete process.env.SOCKS_PROXY_URL 36 | 37 | let args = argsFromEnv() 38 | 39 | assert.ok(_isObject(args), 'did not return an object') 40 | assert.ok(_isUndefined(args.agent), 'agent provided although no config on env') 41 | 42 | process.env.SOCKS_PROXY_URL = url 43 | 44 | args = argsFromEnv() 45 | 46 | assert.ok(_isObject(args), 'did not return an object') 47 | assert.ok((args.agent instanceof SocksProxyAgent), 'did not provide a SocksProxyAgent instance') 48 | assert.ok(/localhost/.test(args.agent.proxy.host), 'provided agent does not use proxy url from env') 49 | }) 50 | 51 | it('provides a connection url only if available', () => { 52 | const url = 'localhost:8080' 53 | delete process.env.TEST_URL 54 | 55 | let args = argsFromEnv('TEST_URL') 56 | 57 | assert.ok(_isObject(args), 'did not return an object') 58 | assert.ok(_isUndefined(args.url), 'url provided although no config on env') 59 | 60 | process.env.TEST_URL = url 61 | 62 | args = argsFromEnv('TEST_URL') 63 | 64 | assert.ok(_isObject(args), 'did not return an object') 65 | assert.ok(_isString(args.url) && !_isEmpty(args.url), 'connection url not pulled from env') 66 | assert.strictEqual(args.url, url, 'provided url does not match env var') 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /examples/rest2/wallets.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _uniq = require('lodash/uniq') 4 | const _capitalize = require('lodash/capitalize') 5 | const _isFinite = require('lodash/isFinite') 6 | const _isEmpty = require('lodash/isEmpty') 7 | const { prepareAmount } = require('bfx-api-node-util') 8 | const { RESTv2 } = require('../../index') 9 | const { args: { apiKey, apiSecret }, debug, debugTable } = require('../util/setup') 10 | 11 | async function execute () { 12 | const rest = new RESTv2({ 13 | apiKey, 14 | apiSecret, 15 | transform: true 16 | }) 17 | const { 18 | valueCCY, hideZeroBalances, filterByType, filterByCurrency 19 | } = { 20 | hideZeroBalances: true, 21 | filterByType: false, 22 | filterByCurrency: false, 23 | valueCCY: 'USD' 24 | } 25 | 26 | const symbolForWallet = w => `t${w.currency}${valueCCY}` 27 | 28 | debug('fetching balances') 29 | 30 | const allWallets = await rest.wallets() // actual balance fetch 31 | const balances = allWallets.filter(w => !( // filter as requested 32 | (hideZeroBalances && +w.balance === 0) || 33 | (!_isEmpty(filterByType) && (w.type.toLowerCase() !== filterByType.toLowerCase())) || 34 | (!_isEmpty(filterByCurrency) && (w.currency.toLowerCase() !== filterByCurrency.toLowerCase())) 35 | )).map(w => ({ 36 | ...w, 37 | currency: w.currency.toUpperCase(), 38 | inValueCurrency: w.currency.toUpperCase() === valueCCY 39 | })) 40 | 41 | if (balances.length === 0) { 42 | return debug('no wallets match provided filters') 43 | } 44 | 45 | debug('found %d balances', balances.length) 46 | 47 | // Pull in ticker data for balances which are not in the requested value ccy 48 | // Balance in BTC, value in USD -> We need to fetch tBTCUSD (last price) 49 | const lastPrices = {} 50 | const balancesToConvert = balances.filter(w => w.currency !== valueCCY) 51 | const symbols = _uniq(balancesToConvert.map(symbolForWallet)) 52 | 53 | if (symbols.length > 0) { 54 | debug('fetching tickers for: %s', symbols.join(', ')) 55 | const tickers = await rest.tickers(symbols) 56 | tickers.forEach(({ symbol, lastPrice }) => (lastPrices[symbol] = +lastPrice)) 57 | } 58 | 59 | let totalValue = 0 60 | const rows = balances.map(({ currency, type, balance, balanceAvailable }) => { 61 | const value = currency !== valueCCY 62 | ? (lastPrices[symbolForWallet({ currency })] * +balance) || 0 63 | : +balance 64 | 65 | totalValue += value 66 | 67 | return [ 68 | _capitalize(type), 69 | currency, 70 | prepareAmount(balance), 71 | prepareAmount(balanceAvailable), 72 | 73 | ...(_isFinite(value) 74 | ? [ 75 | prepareAmount(value), 76 | currency !== valueCCY 77 | ? prepareAmount(lastPrices[symbolForWallet({ currency })]) 78 | : 1 79 | ] 80 | : [ 81 | '-', 82 | '-' 83 | ]) 84 | ] 85 | }) 86 | 87 | debugTable({ 88 | rows, 89 | headers: [ 90 | 'Type', 'Symbol', 'Total', 'Available', `Value (${valueCCY})`, 91 | `Unit Price (${valueCCY})` 92 | ] 93 | }) 94 | 95 | debug('total value: %d %s', prepareAmount(totalValue), valueCCY) 96 | } 97 | 98 | execute() 99 | -------------------------------------------------------------------------------- /docs/scripts/search.js: -------------------------------------------------------------------------------- 1 | 2 | var searchAttr = 'data-search-mode'; 3 | function contains(a,m){ 4 | return (a.textContent || a.innerText || "").toUpperCase().indexOf(m) !== -1; 5 | }; 6 | 7 | //on search 8 | document.getElementById("nav-search").addEventListener("keyup", function(event) { 9 | var search = this.value.toUpperCase(); 10 | 11 | if (!search) { 12 | //no search, show all results 13 | document.documentElement.removeAttribute(searchAttr); 14 | 15 | document.querySelectorAll("nav > ul > li:not(.level-hide)").forEach(function(elem) { 16 | elem.style.display = "block"; 17 | }); 18 | 19 | if (typeof hideAllButCurrent === "function"){ 20 | //let's do what ever collapse wants to do 21 | hideAllButCurrent(); 22 | } else { 23 | //menu by default should be opened 24 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) { 25 | elem.style.display = "block"; 26 | }); 27 | } 28 | } else { 29 | //we are searching 30 | document.documentElement.setAttribute(searchAttr, ''); 31 | 32 | //show all parents 33 | document.querySelectorAll("nav > ul > li").forEach(function(elem) { 34 | elem.style.display = "block"; 35 | }); 36 | document.querySelectorAll("nav > ul").forEach(function(elem) { 37 | elem.style.display = "block"; 38 | }); 39 | //hide all results 40 | document.querySelectorAll("nav > ul > li > ul li").forEach(function(elem) { 41 | elem.style.display = "none"; 42 | }); 43 | //show results matching filter 44 | document.querySelectorAll("nav > ul > li > ul a").forEach(function(elem) { 45 | if (!contains(elem.parentNode, search)) { 46 | return; 47 | } 48 | elem.parentNode.style.display = "block"; 49 | }); 50 | //hide parents without children 51 | document.querySelectorAll("nav > ul > li").forEach(function(parent) { 52 | var countSearchA = 0; 53 | parent.querySelectorAll("a").forEach(function(elem) { 54 | if (contains(elem, search)) { 55 | countSearchA++; 56 | } 57 | }); 58 | 59 | var countUl = 0; 60 | var countUlVisible = 0; 61 | parent.querySelectorAll("ul").forEach(function(ulP) { 62 | // count all elements that match the search 63 | if (contains(ulP, search)) { 64 | countUl++; 65 | } 66 | 67 | // count all visible elements 68 | var children = ulP.children 69 | for (i=0; i ul.collapse_top").forEach(function(parent) { 86 | var countVisible = 0; 87 | parent.querySelectorAll("li").forEach(function(elem) { 88 | if (elem.style.display !== "none") { 89 | countVisible++; 90 | } 91 | }); 92 | 93 | if (countVisible == 0) { 94 | //has no child at all and does not contain text 95 | parent.style.display = "none"; 96 | } 97 | }); 98 | } 99 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { RESTv1, RESTv2 } = require('bfx-api-node-rest') 4 | const WSv1 = require('bfx-api-node-ws1') 5 | const WSv2 = require('./lib/transports/ws2') 6 | const WS2Manager = require('./lib/ws2_manager') 7 | 8 | /** 9 | * Provides access to versions 1 & 2 of the HTTP & WebSocket Bitfinex APIs 10 | */ 11 | class BFX { 12 | /** 13 | * @param {object} [opts] - options 14 | * @param {string} [opts.apiKey] - API key 15 | * @param {string} [opts.apiSecret] - API secret 16 | * @param {string} [opts.authToken] - optional auth option 17 | * @param {string} [opts.company] - optional auth option 18 | * @param {boolean} [opts.transform] - if true, packets are converted to models 19 | * @param {object} [opts.ws] - ws transport options 20 | * @param {object} [opts.rest] - rest transport options 21 | */ 22 | constructor (opts = { 23 | apiKey: '', 24 | apiSecret: '', 25 | authToken: '', 26 | company: '', 27 | transform: false, 28 | ws: {}, 29 | rest: {} 30 | }) { 31 | if (opts.constructor.name !== 'Object') { 32 | throw new Error([ 33 | 'constructor takes an object since version 2.0.0, see:', 34 | 'https://github.com/bitfinexcom/bitfinex-api-node#version-200-breaking-changes\n' 35 | ].join('\n')) 36 | } 37 | 38 | this._apiKey = opts.apiKey || '' 39 | this._apiSecret = opts.apiSecret || '' 40 | this._authToken = opts.authToken || '' 41 | this._company = opts.company || '' 42 | this._transform = opts.transform === true 43 | this._wsArgs = opts.ws || {} 44 | this._restArgs = opts.rest || {} 45 | this._transportCache = { 46 | rest: {}, 47 | ws: {} 48 | } 49 | } 50 | 51 | /** 52 | * Returns an arguments map ready to pass to a transport constructor 53 | * 54 | * @param {object} extraOpts - options to pass to transport 55 | * @returns {object} payload 56 | */ 57 | _getTransportPayload (extraOpts) { 58 | return { 59 | apiKey: this._apiKey, 60 | apiSecret: this._apiSecret, 61 | authToken: this._authToken, 62 | company: this._company, 63 | transform: this._transform, 64 | ...extraOpts 65 | } 66 | } 67 | 68 | /** 69 | * Returns a new REST API class instance (cached by version) 70 | * 71 | * @param {number} [version] - 1 or 2 (default) 72 | * @param {object} [extraOpts] - passed to transport constructor 73 | * @returns {RESTv1|RESTv2} transport 74 | */ 75 | rest (version = 2, extraOpts = {}) { 76 | if (version !== 1 && version !== 2) { 77 | throw new Error(`invalid http API version: ${version}`) 78 | } 79 | 80 | const key = `${version}|${JSON.stringify(extraOpts)}` 81 | 82 | if (!this._transportCache.rest[key]) { 83 | Object.assign(extraOpts, this._restArgs) 84 | const payload = this._getTransportPayload(extraOpts) 85 | 86 | this._transportCache.rest[key] = version === 2 87 | ? new RESTv2(payload) 88 | : new RESTv1(payload) 89 | } 90 | 91 | return this._transportCache.rest[key] 92 | } 93 | 94 | /** 95 | * Returns a new WebSocket API class instance (cached by version) 96 | * 97 | * @param {number} [version] - 1 or 2 (default) 98 | * @param {object} [extraOpts] - passed to transport constructor 99 | * @returns {WSv1|WSv2} transport 100 | */ 101 | ws (version = 2, extraOpts = {}) { 102 | if (version !== 1 && version !== 2) { 103 | throw new Error(`invalid websocket API version: ${version}`) 104 | } 105 | 106 | const key = `${version}|${JSON.stringify(extraOpts)}` 107 | 108 | if (!this._transportCache.ws[key]) { 109 | Object.assign(extraOpts, this._wsArgs) 110 | const payload = this._getTransportPayload(extraOpts) 111 | 112 | this._transportCache.ws[key] = version === 2 113 | ? new WSv2(payload) 114 | : new WSv1(payload) 115 | } 116 | 117 | return this._transportCache.ws[key] 118 | } 119 | } 120 | 121 | module.exports = BFX 122 | module.exports.RESTv1 = RESTv1 123 | module.exports.RESTv2 = RESTv2 124 | module.exports.WSv1 = WSv1 125 | module.exports.WSv2 = WSv2 126 | module.exports.WS2Manager = WS2Manager 127 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const assert = require('assert') 5 | const BFX = require('../index') 6 | const { RESTv1, RESTv2 } = require('bfx-api-node-rest') 7 | const WSv1 = require('bfx-api-node-ws1') 8 | const WSv2 = require('../lib/transports/ws2') 9 | 10 | describe('BFX', () => { 11 | it('should be loaded', () => { 12 | assert.strictEqual(typeof BFX, 'function') 13 | }) 14 | 15 | describe('constructor', () => { 16 | it('throws on using the deprecated way to set options', () => { 17 | assert.throws(() => new BFX(2, {})) 18 | assert.throws(() => new BFX('dummy', 'dummy', 2)) 19 | }) 20 | }) 21 | 22 | describe('rest', () => { 23 | it('throws an error if an invalid version is requested', () => { 24 | const bfx = new BFX() 25 | assert.throws(bfx.rest.bind(bfx, 0)) 26 | assert.throws(bfx.rest.bind(bfx, 3)) 27 | }) 28 | 29 | it('returns correct REST api by version', () => { 30 | const bfx = new BFX() 31 | const restDefault = bfx.rest() 32 | const rest1 = bfx.rest(1) 33 | const rest2 = bfx.rest(2) 34 | 35 | assert(restDefault instanceof RESTv2) 36 | assert(rest1 instanceof RESTv1) 37 | assert(rest2 instanceof RESTv2) 38 | }) 39 | 40 | it('passes API keys & transform flag to new transport', () => { 41 | const bfx = new BFX({ 42 | apiKey: 'k', 43 | apiSecret: 's', 44 | transform: true, 45 | rest: { 46 | url: 'http://' 47 | } 48 | }) 49 | 50 | const rest1 = bfx.rest(1) 51 | const rest2 = bfx.rest(2) 52 | 53 | assert.strictEqual(rest1._apiKey, 'k') 54 | assert.strictEqual(rest2._apiKey, 'k') 55 | assert.strictEqual(rest1._apiSecret, 's') 56 | assert.strictEqual(rest2._apiSecret, 's') 57 | assert.strictEqual(rest1._url, 'http://') 58 | assert.strictEqual(rest2._url, 'http://') 59 | assert.strictEqual(rest2._transform, true) 60 | }) 61 | 62 | it('passes extra options to new transport', () => { 63 | const bfx = new BFX() 64 | const rest2 = bfx.rest(2, { url: '/dev/null' }) 65 | assert.strictEqual(rest2._url, '/dev/null') 66 | }) 67 | 68 | it('returns one instance if called twice for the same version', () => { 69 | const bfx = new BFX() 70 | const restA = bfx.rest(2) 71 | const restB = bfx.rest(2) 72 | assert(restA === restB) 73 | }) 74 | }) 75 | 76 | describe('ws', () => { 77 | it('throws an error if an invalid version is requested', () => { 78 | const bfx = new BFX() 79 | assert.throws(bfx.ws.bind(bfx, 0)) 80 | assert.throws(bfx.ws.bind(bfx, 3)) 81 | }) 82 | 83 | it('returns correct WebSocket api by version', () => { 84 | const bfx = new BFX() 85 | const wsDefault = bfx.ws() 86 | const ws1 = bfx.ws(1) 87 | const ws2 = bfx.ws(2) 88 | 89 | assert(wsDefault instanceof WSv2) 90 | assert(ws1 instanceof WSv1) 91 | assert(ws2 instanceof WSv2) 92 | }) 93 | 94 | it('passes API keys & transform flag to new transport', () => { 95 | const bfx = new BFX({ 96 | apiKey: 'k', 97 | apiSecret: 's', 98 | transform: true, 99 | ws: { 100 | url: 'wss://' 101 | } 102 | }) 103 | 104 | const ws1 = bfx.ws(1) 105 | const ws2 = bfx.ws(2) 106 | 107 | assert.strictEqual(ws1._apiKey, 'k') 108 | assert.strictEqual(ws2._authArgs.apiKey, 'k') 109 | assert.strictEqual(ws1._apiSecret, 's') 110 | assert.strictEqual(ws2._authArgs.apiSecret, 's') 111 | assert.strictEqual(ws1._url, 'wss://') 112 | assert.strictEqual(ws2._url, 'wss://') 113 | assert.strictEqual(ws2._transform, true) 114 | }) 115 | 116 | it('passes extra options to new transport', () => { 117 | const bfx = new BFX() 118 | const ws2 = bfx.ws(2, { url: '/dev/null' }) 119 | assert.strictEqual(ws2._url, '/dev/null') 120 | }) 121 | 122 | it('returns one instance if called twice for the same version', () => { 123 | const bfx = new BFX() 124 | const wsA = bfx.ws(2) 125 | const wsB = bfx.ws(2) 126 | 127 | assert(wsA === wsB) 128 | }) 129 | }) 130 | }) 131 | -------------------------------------------------------------------------------- /test/fixtures/response-trades-pairs.json: -------------------------------------------------------------------------------- 1 | [[32179419,1494853783000,-0.04824433,1760.2],[32179413,1494853779000,0.03689378,1762.9],[32179410,1494853778000,-2,1763.1],[32179398,1494853774000,0.03694043,1763.4],[32179395,1494853772000,0.0170008,1763.4],[32179393,1494853768000,0.3134,1763.4],[32179392,1494853766000,0.1717,1763.4],[32179391,1494853765000,0.03674472,1763.4],[32179390,1494853765000,0.00009347,1763.4],[32179387,1494853763000,0.1717,1763.4],[32179385,1494853760000,0.5,1763.4],[32179384,1494853758000,0.2807,1763.4],[32179377,1494853756000,-0.248183,1762.9],[32179376,1494853754000,0.2582,1763.4],[32179375,1494853752000,-0.285902,1762.9],[32179374,1494853751000,0.21930653,1763.4],[32179373,1494853751000,0.28069347,1762.9],[32179372,1494853748000,0.5,1762.9],[32179371,1494853748000,0.01096553,1762.9],[32179359,1494853745000,0.1525,1762.9],[32179358,1494853731000,-0.101043,1762.7],[32179345,1494853700000,0.5,1762.9],[32179344,1494853698000,0.05,1763.4],[32179342,1494853696000,0.04711822,1763.3],[32179341,1494853696000,0.05288178,1762.8],[32179339,1494853695000,0.86873374,1762.8],[32179338,1494853694000,0.09,1762.8],[32179337,1494853693000,0.185,1762.8],[32179336,1494853690000,0.036,1762.8],[32179335,1494853689000,0.03595638,1762.8],[32179334,1494853689000,0.00004362,1762.3],[32179333,1494853689000,0.036,1762.3],[32179330,1494853686000,0.01868959,1762.3],[32179328,1494853686000,0.01868809,1762.3],[32179327,1494853686000,0.3,1762.3],[32179324,1494853685000,0.01868935,1762.3],[32179317,1494853685000,0.01868935,1762.3],[32179303,1494853683000,0.05,1762.3],[32179302,1494853681000,0.0721,1762.3],[32179301,1494853678000,0.1871,1762.3],[32179300,1494853676000,0.5,1762.3],[32179298,1494853673000,0.3,1762.3],[32179295,1494853663000,0.96,1762.4],[32179297,1494853663000,0.4070081,1762.8],[32179296,1494853663000,0.7534,1762.7],[32179294,1494853663000,0.24999,1762.4],[32179293,1494853663000,0.32344047,1762.3],[32179281,1494853657000,0.01616872,1762.3],[32179280,1494853657000,0.48020443,1762.2],[32179275,1494853656000,0.52038848,1762.2],[32179274,1494853656000,0.00830055,1761.7],[32179270,1494853651000,1.66872777,1761],[32179271,1494853651000,1.59169945,1761.7],[32179263,1494853649000,0.1742,1761],[32179262,1494853647000,0.4539,1761],[32179259,1494853645000,0.5,1761],[32179258,1494853644000,0.64862543,1761],[32179248,1494853643000,0.131,1761],[32179246,1494853642000,1.2488468,1761],[32179245,1494853641000,0.1747,1761],[32179243,1494853638000,1.00097535,1762],[32179242,1494853638000,0.1,1762],[32179241,1494853638000,0.00089345,1761.7],[32179240,1494853638000,0.39081416,1761.7],[32179236,1494853635000,0.5,1761.1],[32179234,1494853627000,0.079,1761.1],[32179233,1494853627000,0.421,1760],[32179232,1494853625000,0.0571,1760],[32179231,1494853621000,0.5,1760],[32179230,1494853619000,0.5,1760],[32179229,1494853618000,0.3587,1760],[32179228,1494853616000,0.5,1760],[32179227,1494853614000,0.5,1760],[32179226,1494853611000,0.5,1760],[32179225,1494853609000,0.5,1760],[32179224,1494853608000,0.5,1760],[32179223,1494853606000,0.5,1760],[32179222,1494853604000,0.076,1760],[32179221,1494853602000,0.0872,1760],[32179220,1494853594000,1.15228897,1760.4],[32179219,1494853594000,0.08998646,1760.2],[32179218,1494853594000,0.29275122,1760],[32179216,1494853593000,3.18635482,1760],[32179212,1494853568000,0.21838758,1760],[32179205,1494853562000,1.6,1759.9],[32179206,1494853562000,2.25250638,1760],[32179204,1494853562000,0.963884,1759.9],[32179203,1494853562000,1.2279,1759.8],[32179183,1494853513000,0.63707354,1759.2],[32179182,1494853513000,0.01209112,1758.4],[32179179,1494853508000,0.08790888,1758.4],[32179175,1494853499000,-0.92759884,1756.2],[32179172,1494853499000,-1.6,1756.8],[32179174,1494853499000,-0.9,1756.6],[32179173,1494853499000,-0.1,1756.6],[32179171,1494853499000,-1.23325116,1757.5],[32179170,1494853499000,-1.5752,1758.1],[32179169,1494853499000,-0.3882,1758.2],[32179168,1494853494000,0.01001354,1760.2],[32179166,1494853493000,-0.21,1759],[32179167,1494853493000,-0.33556,1757.9],[32179165,1494853493000,-0.7763,1759.1],[32179163,1494853489000,0.03756071,1760.3],[32179161,1494853487000,-0.32865189,1757.5],[32179160,1494853487000,-1.12193211,1757.7],[32179157,1494853487000,-0.1,1758.4],[32179158,1494853487000,-1.5033,1758],[32179159,1494853487000,-0.911706,1757.9],[32179155,1494853485000,0.02170315,1761.1],[32179149,1494853472000,0.5118,1761],[32179150,1494853472000,0.50505948,1761.1],[32179144,1494853468000,1.10829239,1761.7],[32179143,1494853468000,1.54315026,1761.1],[32179142,1494853468000,1.6273,1760.5],[32179141,1494853468000,0.1,1760.2],[32179140,1494853468000,1.71930627,1759.8],[32179138,1494853468000,0.08539655,1759.7],[32179139,1494853468000,0.92260345,1759.7],[32179124,1494853467000,-1.09071521,1756.8],[32179123,1494853467000,-0.02894644,1756.9]] 2 | -------------------------------------------------------------------------------- /test/fixtures/response-trades-funding.json: -------------------------------------------------------------------------------- 1 | [[6735741,1494853437352,-7342.32130201,0.00179797,30],[6735740,1494853437232,-6.22726787,0.00179797,30],[6735734,1494853434026,-4078.57360794,0.00179797,30],[6735730,1494853432487,-1438.49972447,0.00179797,30],[6735729,1494853431991,-195.61999655,0.00179797,30],[6735728,1494853431651,-2219.23318615,0.00179797,30],[6735727,1494853431627,-74.47875318,0.00179797,5],[6735726,1494853431609,-1234.13448145,0.00179797,30],[6735725,1494853431595,-66.99,0.00179797,30],[6735724,1494853431581,-56.22,0.00179797,30],[6735723,1494853431564,-233.66,0.00179797,30],[6735722,1494853431549,-95.94,0.00179797,30],[6735721,1494853431532,-591.74236565,0.00179794,30],[6735720,1494853431514,-513.37126375,0.00179794,30],[6735719,1494853431501,-59.08787049,0.00179792,2],[6735718,1494853431486,-59.08787049,0.00179791,30],[6735717,1494853431473,-50.49751111,0.00179787,30],[6735716,1494853431461,-50.49751111,0.00179787,30],[6735715,1494853431445,-100.99502222,0.00179787,30],[6735714,1494853431426,-2039.12850002,0.00179,30],[6735713,1494853431405,-183.88803906,0.00179,30],[6735711,1494853430206,-1855.24046096,0.00179,30],[6735710,1494853430193,-53.4042344,0.00152855,2],[6735709,1494853430181,-53.4042344,0.00152855,30],[6735708,1494853430165,-52.02165252,0.00093527,30],[6735707,1494853430150,-499.56735807,0.00093527,15],[6735706,1494853430137,-818.34204076,0.00093527,30],[6735705,1494853430123,-195.62001798,0.00093405,2],[6735695,1494853428508,-37.16518202,0.00093405,2],[6735688,1494853141068,-124.34634957,0.00178999,30],[6735687,1494853140987,-50.48604426,0.0017899,30],[6735686,1494853140978,-50.48604426,0.0017899,30],[6735685,1494853140967,-50.48604426,0.0017898,30],[6735684,1494853140957,-51.25765085,0.00160388,30],[6735683,1494853140914,-50.48604426,0.00160378,30],[6735682,1494853140813,-50.48604426,0.00160378,30],[6735681,1494853140801,-50.48604426,0.00160378,30],[6735680,1494853140790,-49.47973402,0.00160378,30],[6735679,1494853140761,-1.00631024,0.00160378,30],[6735678,1494853140748,-50.48604426,0.00160378,30],[6735677,1494853140726,-373.08499213,0.00160085,2],[6735676,1494853140716,-373.08499213,0.00160085,2],[6735675,1494853140702,-373.08499213,0.00160085,2],[6735674,1494853140690,-373.08499213,0.00160085,2],[6735673,1494853140679,-373.08499213,0.00160085,2],[6735672,1494853140664,-373.08499213,0.00160085,2],[6735671,1494853140652,-373.08499213,0.00160085,2],[6735670,1494853140633,-373.08499213,0.00160085,2],[6735669,1494853140617,-373.08499213,0.00160085,2],[6735668,1494853140603,-373.08499213,0.00160085,2],[6735667,1494853140591,-373.08499213,0.00160085,2],[6735666,1494853140577,-373.08499213,0.00160085,2],[6735665,1494853140564,-373.08499213,0.00160085,2],[6735664,1494853140551,-373.08499213,0.00160085,2],[6735663,1494853140532,-373.08499213,0.00160085,2],[6735662,1494853140513,-70.43,0.00158165,2],[6735661,1494853140500,-50.48604426,0.00142968,30],[6735660,1494853140486,-50.48604426,0.00142968,30],[6735659,1494853140468,-50.48604426,0.00142968,30],[6735658,1494853140456,-50.48604426,0.00142968,30],[6735657,1494853140445,-182.48,0.00140436,30],[6735656,1494853140432,-2462.69,0.00140436,30],[6735655,1494853140417,-1480.11391365,0.00125,30],[6735654,1494853140399,-50.48604426,0.0012499,30],[6735653,1494853140378,-50.48604426,0.0012499,30],[6735652,1494853140363,-51.25765085,0.0012,30],[6735651,1494853140345,-3740.90039169,0.0012,30],[6735650,1494853140330,-245.20955164,0.0012,30],[6735649,1494853140316,-117.84692501,0.0012,2],[6735648,1494853140300,-240.40288527,0.00119999,30],[6735647,1494853140286,-74.42092493,0.0011999,30],[6735646,1494853140273,-171.64801902,0.0011999,30],[6735645,1494853140256,-50.48604426,0.0011999,30],[6735644,1494853140243,-50.48604426,0.0011999,30],[6735643,1494853140228,-50.48604426,0.0011999,30],[6735641,1494853140215,-50.48604426,0.0011999,30],[6735639,1494853140202,-50.48604426,0.0011999,30],[6735636,1494853140181,-6508.92016589,0.00114717,30],[6735634,1494853140139,-566.49659196,0.00114717,30],[6735632,1494853140079,-0.82473213,0.00114717,30],[6735631,1494853139695,-324.49232558,0.00114717,30],[6735630,1494853139675,-26.64483104,0.00114717,30],[6735629,1494853139644,-895.94471993,0.00114717,30],[6735628,1494853139625,-571.02559163,0.00114717,30],[6735627,1494853139607,-739.67795707,0.00114714,2],[6735626,1494853139594,-2068,0.00107009,7],[6735625,1494853139577,-50.48604426,0.00093634,7],[6735624,1494853139563,-50.48604426,0.00093634,7],[6735623,1494853139549,-50.48604426,0.00093634,7],[6735622,1494853139534,-50.48604426,0.00093634,7],[6735621,1494853139521,-50.48604426,0.00093634,7],[6735620,1494853139507,-50.48604426,0.00093631,7],[6735619,1494853139489,-147.90174828,0.000936,14],[6735617,1494853139470,-51.25765085,0.00093589,30],[6735615,1494853139454,-50.48604426,0.00093584,7],[6735612,1494853139425,-455.25010385,0.00093567,2],[6735610,1494853139409,-56.21,0.00093567,2],[6735609,1494853139397,-700.99,0.00093567,30],[6735607,1494853139385,-95.94,0.00093567,30],[6735605,1494853139373,-50.48604426,0.00093557,7],[6735603,1494853139360,-50.48604426,0.00093557,7],[6735602,1494853139347,-56.07612898,0.00093507,30],[6735601,1494853139330,-51.25765085,0.00093507,30],[6735600,1494853139314,-51.25765085,0.00093507,30],[6735599,1494853139303,-51.25765085,0.00093507,30],[6735598,1494853139285,-538.77227604,0.00093505,5],[6735597,1494853139265,-68.7371996,0.00093505,5],[6735596,1494853139251,-274.37411766,0.00093505,2],[6735595,1494853139240,-3773.43737221,0.00093505,30],[6735594,1494853139227,-6283.68710181,0.00093505,30],[6735593,1494853139213,-7302.52088507,0.00093505,30],[6735592,1494853139192,-72.69342505,0.00093505,30],[6735591,1494853139175,-3670.70993709,0.00093505,30],[6735590,1494853139161,-710.97877232,0.00093505,5],[6735589,1494853139139,-1750.68218205,0.00093505,30],[6735588,1494853139120,-5917.26229886,0.00093505,30],[6735587,1494853139105,-4477.98100335,0.00093505,2],[6735586,1494853139088,-52.21529333,0.00093505,30],[6735585,1494853139073,-590.02,0.00093504,2],[6735584,1494853139056,-54.52,0.00093504,2]] 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitfinex WSv2 Trading API for Node.JS - Bitcoin, Ethereum, Ripple and more 2 | 3 | [![Build Status](https://travis-ci.org/bitfinexcom/bitfinex-api-node.svg?branch=master)](https://travis-ci.org/bitfinexcom/bitfinex-api-node) 4 | 5 | A Node.JS reference implementation of the Bitfinex API 6 | 7 | ## Features 8 | 9 | * Official implementation 10 | * REST v2 API 11 | * WebSockets v2 API 12 | * WebSockets v1 API 13 | 14 | ## Installation 15 | 16 | ```bash 17 | npm i --save bitfinex-api-node 18 | ``` 19 | 20 | ### Quickstart 21 | 22 | ```js 23 | const { WSv2 } = require('bitfinex-api-node') 24 | const ws = new WSv2({ transform: true }) 25 | 26 | // do something with ws client 27 | ``` 28 | 29 | ### Docs 30 | 31 | Refer to the [`docs/`](https://cdn.statically.io/gh/bitfinexcom/bitfinex-api-node/master/docs/index.html) 32 | folder for JSDoc-generated HTML documentation, and the [`examples/`](/examples) 33 | folder for executable examples covering common use cases. 34 | 35 | Official API documentation at [https://docs.bitfinex.com/v2/reference](https://docs.bitfinex.com/v2/reference) 36 | 37 | ### Examples 38 | 39 | Sending an order & tracking status: 40 | 41 | ```js 42 | const ws = bfx.ws() 43 | 44 | ws.on('error', (err) => console.log(err)) 45 | ws.on('open', ws.auth.bind(ws)) 46 | 47 | ws.once('auth', () => { 48 | const o = new Order({ 49 | cid: Date.now(), 50 | symbol: 'tETHUSD', 51 | amount: 0.1, 52 | type: Order.type.MARKET 53 | }, ws) 54 | 55 | // Enable automatic updates 56 | o.registerListeners() 57 | 58 | o.on('update', () => { 59 | console.log(`order updated: ${o.serialize()}`) 60 | }) 61 | 62 | o.on('close', () => { 63 | console.log(`order closed: ${o.status}`) 64 | ws.close() 65 | }) 66 | 67 | o.submit().then(() => { 68 | console.log(`submitted order ${o.id}`) 69 | }).catch((err) => { 70 | console.error(err) 71 | ws.close() 72 | }) 73 | }) 74 | 75 | ws.open() 76 | ``` 77 | 78 | Cancel all open orders 79 | 80 | ```js 81 | const ws = bfx.ws(2) 82 | 83 | ws.on('error', (err) => console.log(err)) 84 | ws.on('open', ws.auth.bind(ws)) 85 | 86 | ws.onOrderSnapshot({}, (orders) => { 87 | if (orders.length === 0) { 88 | console.log('no open orders') 89 | return 90 | } 91 | 92 | console.log(`recv ${orders.length} open orders`) 93 | 94 | ws.cancelOrders(orders).then(() => { 95 | console.log('cancelled orders') 96 | }) 97 | }) 98 | 99 | ws.open() 100 | ``` 101 | 102 | Subscribe to trades by pair 103 | 104 | ```js 105 | const ws = bfx.ws(2) 106 | 107 | ws.on('error', (err) => console.log(err)) 108 | ws.on('open', () => { 109 | ws.subscribeTrades('BTCUSD') 110 | }) 111 | 112 | ws.onTrades({ symbol: 'tBTCUSD' }, (trades) => { 113 | console.log(`trades: ${JSON.stringify(trades)}`) 114 | }) 115 | ws.onTradeEntry({ symbol: 'tBTCUSD' }, (trades) => { 116 | console.log(`te: ${JSON.stringify(trades)}`) 117 | }) 118 | 119 | ws.open() 120 | ``` 121 | 122 | ## Version 2.0.0 Breaking changes 123 | 124 | ### constructor takes only an options object now, including the API keys 125 | 126 | Old: 127 | 128 | ```js 129 | new BFX(API_KEY, API_SECRET, { version: 2 }) 130 | ``` 131 | 132 | since 2.0.0: 133 | 134 | ```js 135 | new BFX({ apiKey: '', apiSecret: '' }) 136 | ``` 137 | 138 | ### `trade` and `orderbook` snapshots are emitted as nested lists 139 | 140 | To make dealing with snapshots better predictable, snapshots are emitted as an array. 141 | 142 | ### normalized orderbooks for R0 143 | 144 | Lists of raw orderbooks (`R0`) are ordered in the same order as `P0`, `P1`, 145 | `P2`, `P3` 146 | 147 | ## Testing 148 | 149 | ```bash 150 | npm test 151 | ``` 152 | 153 | ## FAQ 154 | 155 | ### Order Creation Limits 156 | 157 | The base limit per-user is 1,000 orders per 5 minute interval, and is shared 158 | between all account API connections. It increases proportionally to your trade 159 | volume based on the following formula: 160 | 161 | `1000 + (TOTAL_PAIRS_PLATFORM * 60 * 5) / (250000000 / USER_VOL_LAST_30d)` 162 | 163 | Where `TOTAL_PAIRS_PLATFORM` is the number of pairs shared between 164 | Ethfinex/Bitfinex (currently ~101) and `USER_VOL_LAST_30d` is in USD. 165 | 166 | ### 'on' Packet Guarantees 167 | 168 | No; if your order fills immediately, the first packet referencing the order 169 | will be an `oc` signaling the order has closed. If the order fills partially 170 | immediately after creation, an `on` packet will arrive with a status of 171 | `PARTIALLY FILLED...` 172 | 173 | For example, if you submit a `LIMIT` buy for 0.2 BTC and it is added to the 174 | order book, an `on` packet will arrive via ws2. After a partial fill of 0.1 175 | BTC, an `ou` packet will arrive, followed by a final `oc` after the remaining 176 | 0.1 BTC fills. 177 | 178 | On the other hand, if the order fills immediately for 0.2 BTC, you will only 179 | receive an `oc` packet. 180 | 181 | ### Nonce too small 182 | 183 | I make multiple parallel request and I receive an error that the nonce is too 184 | small. What does it mean? 185 | 186 | Nonces are used to guard against replay attacks. When multiple HTTP requests 187 | arrive at the API with the wrong nonce, e.g. because of an async timing issue, 188 | the API will reject the request. 189 | 190 | If you need to go parallel, you have to use multiple API keys right now. 191 | 192 | ### `te` vs `tu` Messages 193 | 194 | A `te` packet is sent first to the client immediately after a trade has been 195 | matched & executed, followed by a `tu` message once it has completed processing. 196 | During times of high load, the `tu` message may be noticably delayed, and as 197 | such only the `te` message should be used for a realtime feed. 198 | 199 | ### Sequencing 200 | 201 | If you enable sequencing on v2 of the WS API, each incoming packet will have a 202 | public sequence number at the end, along with an auth sequence number in the 203 | case of channel `0` packets. The public seq numbers increment on each packet, 204 | and the auth seq numbers increment on each authenticated action (new orders, 205 | etc). These values allow you to verify that no packets have been missed/dropped, 206 | since they always increase monotonically. 207 | 208 | ### Differences Between R* and P* Order Books 209 | 210 | Order books with precision `R0` are considered 'raw' and contain entries for 211 | each order submitted to the book, whereas `P*` books contain entries for each 212 | price level (which aggregate orders). 213 | 214 | ### Contributing 215 | 216 | 1. Fork it 217 | 2. Create your feature branch (`git checkout -b my-new-feature`) 218 | 3. Commit your changes (`git commit -am 'Add some feature'`) 219 | 4. Push to the branch (`git push origin my-new-feature`) 220 | 5. Create a new Pull Request 221 | -------------------------------------------------------------------------------- /test/lib/transports/ws2-integration.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | 4 | const assert = require('assert') 5 | const WSv2 = require('../../../lib/transports/ws2') 6 | const { Order } = require('bfx-api-node-models') 7 | const { MockWSv2Server } = require('bfx-api-mock-srv') 8 | 9 | const API_KEY = 'dummy' 10 | const API_SECRET = 'dummy' 11 | 12 | const createTestWSv2Instance = (params = {}) => { 13 | return new WSv2({ 14 | apiKey: API_KEY, 15 | apiSecret: API_SECRET, 16 | url: 'ws://localhost:9997', 17 | 18 | ...params 19 | }) 20 | } 21 | 22 | describe('WSv2 integration', () => { 23 | let ws = null 24 | let wss = null 25 | 26 | afterEach(async () => { 27 | try { // may fail due to being modified by a test, it's not a problem 28 | if (ws && ws.isOpen()) { 29 | await ws.close() 30 | } 31 | } catch (e) { 32 | assert.ok(true) 33 | } 34 | 35 | if (wss && wss.isOpen()) { 36 | await wss.close() 37 | } 38 | 39 | ws = null 40 | wss = null 41 | }) 42 | 43 | describe('orders', () => { 44 | it('creates & confirms orders', async () => { 45 | wss = new MockWSv2Server({ listen: true }) 46 | ws = createTestWSv2Instance() 47 | 48 | await ws.open() 49 | await ws.auth() 50 | 51 | const o = new Order({ 52 | gid: null, 53 | cid: 0, 54 | type: 'EXCHANGE LIMIT', 55 | price: 100, 56 | amount: 1, 57 | symbol: 'tBTCUSD' 58 | }) 59 | 60 | return ws.submitOrder(o) 61 | }) 62 | 63 | it('keeps orders up to date', async () => { 64 | wss = new MockWSv2Server({ listen: true }) 65 | ws = createTestWSv2Instance() 66 | 67 | await ws.open() 68 | await ws.auth() 69 | 70 | const o = new Order({ 71 | gid: null, 72 | cid: 0, 73 | type: 'EXCHANGE LIMIT', 74 | price: 100, 75 | amount: 1, 76 | symbol: 'tBTCUSD' 77 | }, ws) 78 | 79 | o.registerListeners() 80 | 81 | await o.submit() 82 | 83 | const arr = o.serialize() 84 | arr[16] = 256 85 | 86 | wss.send([0, 'ou', arr]) 87 | 88 | await new Promise(resolve => setTimeout(resolve, 100)) 89 | 90 | assert.strictEqual(o.price, 256) 91 | arr[16] = 150 92 | 93 | wss.send([0, 'oc', arr]) 94 | 95 | await new Promise(resolve => setTimeout(resolve, 100)) 96 | 97 | assert.strictEqual(o.price, 150) 98 | o.removeListeners() 99 | }) 100 | 101 | it('updateOrder: sends order changeset packet through', async () => { 102 | wss = new MockWSv2Server() 103 | ws = createTestWSv2Instance() 104 | 105 | await ws.open() 106 | await ws.auth() 107 | 108 | let sawMessage = false 109 | const o = new Order({ 110 | id: Date.now(), 111 | type: 'EXCHANGE LIMIT', 112 | price: 100, 113 | amount: 1, 114 | symbol: 'tBTCUSD' 115 | }, ws) 116 | 117 | ws._ws.send = (msgJSON) => { 118 | const msg = JSON.parse(msgJSON) 119 | 120 | assert.strictEqual(msg[0], 0) 121 | assert.strictEqual(msg[1], 'ou') 122 | assert(msg[3]) 123 | assert.strictEqual(msg[3].id, o.id) 124 | assert.strictEqual(+msg[3].delta, 1) 125 | assert.strictEqual(+msg[3].price, 200) 126 | 127 | sawMessage = true 128 | } 129 | 130 | o.update({ price: 200, delta: 1 }) // note promise ignored 131 | assert(sawMessage) 132 | }) 133 | 134 | it('sends individual order packets when not buffering', async () => { 135 | wss = new MockWSv2Server() 136 | ws = createTestWSv2Instance() 137 | 138 | await ws.open() 139 | await ws.auth() 140 | 141 | let sawBothOrders = false 142 | const oA = new Order({ 143 | gid: null, 144 | cid: Date.now(), 145 | type: 'EXCHANGE LIMIT', 146 | price: 100, 147 | amount: 1, 148 | symbol: 'tBTCUSD' 149 | }) 150 | 151 | const oB = new Order({ 152 | gid: null, 153 | cid: Date.now(), 154 | type: 'EXCHANGE LIMIT', 155 | price: 10, 156 | amount: 1, 157 | symbol: 'tETHUSD' 158 | }) 159 | 160 | let sendN = 0 161 | 162 | ws._ws.send = (msgJSON) => { 163 | const msg = JSON.parse(msgJSON) 164 | assert.strictEqual(msg[1], 'on') 165 | sendN++ 166 | 167 | if (sendN === 2) { 168 | sawBothOrders = true 169 | } 170 | } 171 | 172 | // note promises ignored 173 | ws.submitOrder(oA) 174 | ws.submitOrder(oB) 175 | 176 | assert(sawBothOrders) 177 | }) 178 | 179 | it('buffers order packets', async () => { 180 | wss = new MockWSv2Server() 181 | ws = createTestWSv2Instance({ 182 | orderOpBufferDelay: 100 183 | }) 184 | 185 | await ws.open() 186 | await ws.auth() 187 | 188 | const oA = new Order({ 189 | gid: null, 190 | cid: Date.now(), 191 | type: 'EXCHANGE LIMIT', 192 | price: 100, 193 | amount: 1, 194 | symbol: 'tBTCUSD' 195 | }) 196 | 197 | const oB = new Order({ 198 | gid: null, 199 | cid: Date.now(), 200 | type: 'EXCHANGE LIMIT', 201 | price: 10, 202 | amount: 1, 203 | symbol: 'tETHUSD' 204 | }) 205 | 206 | return new Promise((resolve) => { 207 | ws._ws.send = (msgJSON) => { 208 | const msg = JSON.parse(msgJSON) 209 | assert.strictEqual(msg[1], 'ox_multi') 210 | 211 | msg[3].forEach((payload) => { 212 | assert.strictEqual(payload[0], 'on') 213 | }) 214 | 215 | wss.close() 216 | resolve() 217 | } 218 | 219 | // note promises ignored 220 | ws.submitOrder(oA) 221 | ws.submitOrder(oB) 222 | }) 223 | }) 224 | }) 225 | 226 | describe('listeners', () => { 227 | it('manages listeners by cbGID', () => { 228 | ws = createTestWSv2Instance() 229 | ws._channelMap = { 0: { channel: 'auth' } } 230 | 231 | let updatesSeen = 0 232 | ws.onAccountTradeUpdate({ pair: 'BTCUSD', cbGID: 10 }, () => updatesSeen++) 233 | ws.onOrderUpdate({ symbol: 'tBTCUSD', cbGID: 10 }, () => updatesSeen++) 234 | 235 | ws._handleChannelMessage([0, 'tu', [123, 'tBTCUSD']]) 236 | ws._handleChannelMessage([0, 'ou', [0, 0, 0, 'tBTCUSD']]) 237 | ws.removeListeners(10) 238 | ws._handleChannelMessage([0, 'tu', [123, 'tBTCUSD']]) 239 | ws._handleChannelMessage([0, 'ou', [0, 0, 0, 'tBTCUSD']]) 240 | 241 | assert.strictEqual(updatesSeen, 2) 242 | }) 243 | 244 | it('tracks channel refs to auto sub/unsub', async () => { 245 | ws = createTestWSv2Instance() 246 | wss = new MockWSv2Server() 247 | let subs = 0 248 | let unsubs = 0 249 | 250 | await ws.open() 251 | 252 | wss.on('message', (ws, msg) => { 253 | if (msg.event === 'subscribe' && msg.channel === 'trades') { 254 | subs++ 255 | ws.send(JSON.stringify({ 256 | event: 'subscribed', 257 | chanId: 42, 258 | channel: 'trades', 259 | symbol: msg.symbol 260 | })) 261 | } else if (msg.event === 'unsubscribe' && msg.chanId === 42) { 262 | unsubs++ 263 | ws.send(JSON.stringify({ 264 | event: 'unsubscribed', 265 | chanId: 42 266 | })) 267 | } 268 | }) 269 | 270 | ws.subscribeTrades('tBTCUSD') 271 | ws.subscribeTrades('tBTCUSD') 272 | ws.subscribeTrades('tBTCUSD') 273 | 274 | ws.on('subscribed', () => { 275 | ws.unsubscribeTrades('tBTCUSD') 276 | ws.unsubscribeTrades('tBTCUSD') 277 | ws.unsubscribeTrades('tBTCUSD') 278 | ws.unsubscribeTrades('tBTCUSD') 279 | ws.unsubscribeTrades('tBTCUSD') 280 | }) 281 | 282 | return new Promise((resolve) => { 283 | ws.on('unsubscribed', () => { 284 | assert.strictEqual(subs, 1) 285 | assert.strictEqual(unsubs, 1) 286 | resolve() 287 | }) 288 | }) 289 | }) 290 | }) 291 | 292 | describe('info message handling', () => { 293 | it('notifies listeners on matching code', () => { 294 | let sawMaintenanceEnd = false 295 | ws = new WSv2() 296 | 297 | ws.onInfoMessage(WSv2.info.MAINTENANCE_END, () => { 298 | sawMaintenanceEnd = true 299 | }) 300 | 301 | ws._onWSMessage(JSON.stringify({ 302 | event: 'info', 303 | code: WSv2.info.MAINTENANCE_START, 304 | msg: '' 305 | })) 306 | 307 | ws._onWSMessage(JSON.stringify({ 308 | event: 'info', 309 | code: WSv2.info.MAINTENANCE_END, 310 | msg: '' 311 | })) 312 | 313 | assert(sawMaintenanceEnd) 314 | }) 315 | }) 316 | }) 317 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 8.0.0 2 | - upgrade: upgraded bfx-api-node-rest to 7.0.0, breaks previous versions compatibility 3 | - upgrade: upgraded bfx-api-mock-srv to 2.0.0, breaks previous versions compatibility 4 | 5 | 7.0.0 6 | - chore: remove pulse deps 7 | 8 | 6.0.0 9 | - removed unused deps: chai, request-promise, request, crc-32 10 | - removed bluebird Promise 11 | - update params of submitOrder and cancelOrders methods of WS transport to support new rest api signature 12 | - bumped bfx-api-node-rest version up to 5.1.1, breaks previous versions compatibility 13 | - bumped mocha, jsdoc-to-markdown, docdash, blessed-contrib, ws versions to fix vulnerabilities 14 | - moved dev deps readline-promise, blessed, blessed-contrib, cli-table3, p-iteration into corresponding section 15 | 16 | 5.0.4 17 | - fix: public funding trade parsing 18 | - styling: fix code formatting 19 | 20 | 5.0.0 21 | - upgrade: upgraded bfx-api-node-rest to 4.0.0, breaks previous versions compatibility 22 | - fix: jsdocs 23 | 24 | 4.0.17 25 | - added pulse examples 26 | 27 | 4.0.16 28 | - fix: unsubscribe fails depending on channel id type 29 | 30 | 4.0.15 31 | - fix 2 high vulnerabilities, switch from cli-table2 to cli-table3 dependency 32 | 33 | 4.0.14 34 | - fix: README docs reference 35 | 36 | 4.0.13 37 | - meta: mv several moduels to deps from dev-deps for bfx-cli 38 | 39 | 4.0.12 40 | - meta: mv readline-promise to deps from dev-deps 41 | 42 | 4.0.11 43 | - meta: added nyc for coverage file gen 44 | - meta: added husky npm test pre-commit hook 45 | - meta: refactored to async/await instead of Promises where possible 46 | - meta: refactored examples to reduce boilerplate and normalize output 47 | - meta: removed example script runners from package.json 48 | - examples: removed CLI scripts in favour of a dedicated bfx-cli module 49 | - examples: renamed 'orders' to 'list-open-orders' 50 | - examples: positions now always includes P/L 51 | - WSv2: support '*' filter value to match all 52 | - WSv2: added sequencingEnabled() method 53 | - WSv2: added usesAgent() method 54 | - WSv2: added getURL() method 55 | - WSv2: fix in cancelOrders() to prevent context clobber for this.cancelOrder() call 56 | - WSv2: default connection url now exposed on WSv2.url 57 | - WSv2: removed unused prec/len params from unsubscribeOrderBook() 58 | - WSv2: removed unused rawMsg param from _handleEventMessage() 59 | - WSv2: fix getDataChannelId() not filtering by channel type 60 | - WS2Manager: reconnect() and close() now return promises 61 | - WS2Manager: added getAuthArgs() 62 | - WS2Manager: added missing tests 63 | 64 | 4.0.10 65 | - fix: refactor tests so they can run alongside all other HF/API library tests 66 | 67 | 4.0.9 68 | - WS2Manager: respect internal auth arg settings 69 | 70 | 4.0.8 71 | - WSv2: fix on trade message handler by prioritising channel data symbol over pair symbol 72 | 73 | 4.0.7 74 | - WSv2: refactor to use async/await style where possible 75 | - WSv2: reconnect() now always resolves on completion 76 | 77 | 4.0.6 78 | - WSv2: fix internal flag persistence #521 79 | 80 | 4.0.5 81 | - WSv2: add auth arg management (pre-set dms and calc) 82 | - WSv2: add updateAthArgs() 83 | - WSv2: add getAuthArgs() 84 | - WS2Manager: fix auth args handling with new methods 85 | - WS2Manager: rename setAuthCredentials -> setAPICredentials 86 | 87 | 4.0.4 88 | - Orderbook: generate and update book using lossless string format 89 | in order to prevent floating point precision checksum errors: 90 | https://github.com/bitfinexcom/bitfinex-api-node/issues/511 91 | 92 | 4.0.3 93 | - WS2Manager: add setAuthArgs method 94 | 95 | 4.0.2 96 | - WS2Manager: add reconnect method 97 | 98 | 4.0.1 99 | - WSv2: fix fte and ftu event routing 100 | 101 | 4.0.0 102 | - include bfx-api-node-rest takeNotification hotfix. 103 | This pull request changed the schema of data returned from the v2 104 | REST functions. See pr https://github.com/bitfinexcom/bfx-api-node-rest/pull/42 105 | for more info 106 | 107 | 3.0.2 108 | - WSv2: affCode support 109 | 110 | 3.0.1 111 | - docs: update 112 | 113 | 3.0.0 114 | - Updates function rest2.withdraw to v2 functionality 115 | - Updates function rest2.transfer to v2 functionality 116 | - adds function rest2.getDepositAddress 117 | - adds function rest2.submitAutoFunding 118 | - adds function rest2.closeFunding 119 | - adds function rest2.cancelFundingOffer 120 | - adds function rest2.submitFundingOffer 121 | - adds function rest2.claimPosition 122 | - adds function rest2.cancelOrder 123 | - adds function rest2.updateOrder 124 | - adds function rest2.submitOrder 125 | 126 | 2.0.10 127 | - WSv2: ignore notification auth seq numbers (no longer provided by API) 128 | 129 | 2.0.9 130 | 131 | - WS2Manager: add managedUnsubscribe() 132 | - WS2Manager: add close() 133 | - WS2Manager: add getAuthenticatedSocket() 134 | - WSv2: add suppport for liquidations feed (status methods) 135 | - WSv2: add reconnect throttler in case of connection reset w/ many open sockets 136 | 137 | 2.0.8 138 | 139 | - Bump dependency versions 140 | 141 | 2.0.7 142 | 143 | - WSv2: increase data chan limit to 30 (732499b) 144 | 145 | 2.0.6 146 | 147 | - WSv2: decrease data chan limit to 25 (6816992) 148 | - add close-positions script (face1fc) 149 | - add symbol-details script (708849e) 150 | - add currencies script (cff1647) 151 | - add funding info fetch example (337f202) 152 | - standard --fix (5e6f786, fb5e319, b56b157) 153 | - fix lastMidPrice in example courtesy of MowgliB (004f904) 154 | 155 | 2.0.5 156 | 157 | - WSv2: improve reconnect functionality courtesy of cwolters (950105d) 158 | - WSv2: add funding info example (b597c4d) 159 | - WSv2: add order creation w/ TIF example (f25df58) 160 | - bump dep versions (5e4d439, d72d56f) 161 | - mv babel deps to dev-deps (a576c57) 162 | 163 | 2.0.4 164 | 165 | - add symbols back into ws2 ticker messages [models updated] (1f4a7eb) 166 | 167 | 2.0.3 168 | 169 | - add browser builds (e651496) 170 | - add errors in case of missing chan sub filters (4607154) 171 | - remove symbols from ws2 ticker messages (06b0e13) 172 | 173 | 2.0.2 174 | 175 | - improve logging (ceddd87, 404bd7a) 176 | - export WS2Manager class (afcdefe) 177 | 178 | 2.0.1 179 | 180 | - extract most logic into external libraries (13edff8) 181 | - add support for all currencies in funding offer/loan/credit history (e39f360) 182 | - add automatic re-subscribe on reconnect (e4f65ec) 183 | - add withAllSockets method to manager (90c7fd5) 184 | - split trades listeners between public and private trades (3a428a6) 185 | - allow multiple nonce consumers (2a51dcd) 186 | - REST API v2: add currencies method (122648a) 187 | - OrderBook: add funding support (d8572a6) 188 | - LedgerEntry: add wallet (e5b91c5) 189 | - and more! 190 | 191 | 2.0.0 192 | 193 | - added CLI commands (971e8bf) 194 | - added TradingTicker model (1099273) 195 | - added model class transform support to RESTv2 (1099273) 196 | - added ability to unserialize objects in Model.unserialize() (b23a576) 197 | - added ledgers & movements examples (176d5a9) 198 | - filled in FundingInfo model (268ecc9) 199 | - updated MarginInfo model indices (268ecc9) 200 | - increased max WSv2 listener limit to 1k (5ade818) 201 | - REST API v2: fix calc balances API path (5e2f834) 202 | - WS API v2: added notifyUI helper to generate broadcasts (22cb5bc) 203 | - WS API v2: added support for DMS flag in auth (11e57b1) 204 | - WS API v2: added socket manager for auto multiplexing (f693bb9) 205 | - WS API v2: fixed error notification seq # tracking (1b1c1f3) 206 | - WS API v2: fixed trades event name resolution w/ seq numbers (46af211) 207 | - REST API v2: added ability to auth via token (07f8756) 208 | - REST API v2: added ability to fetch order history for all symbols (57f8c7b) 209 | - REST API v2: added ability to fetch account trades for all symbols (14b13c1) 210 | - REST API v2: added user info endpoint & associated model (36c0079) 211 | - OrderBook: fixed unserialization for raw books (01b5ce4) 212 | - OrderBook: removal of unknown entries no longer raises an error (7bd5bc2) 213 | - OrderBook: array sort is maintained on update (520a9a0) 214 | - OrderBook: converts exp notation numbers to fixed for checksum (2c8487c) 215 | - and more! 216 | 217 | 2.0.0-beta.1 218 | 219 | - refactored general model handling (broke out field indexes) (c616696) 220 | - REST API v1: add support for close position endpoint (14db6fe) 221 | - REST API v2: added query param support to the candles() handler (be779c3) 222 | - REST API v2: added platform status endpoint (5e3fe56) 223 | - WS API v2: clean up channel subscriptions on open/close (7c17b96, 92ce89d) 224 | - WS API v2: now passes update packet & order to Order model events (c616696) 225 | - WS API v2: added support for new order flags (79c4a40, 3406ac3) 226 | - WS API v2: added support for filtering by id to order event listeners (be779c3) 227 | - WS API v2: added support for managed order book checksum verification (cab9635) 228 | - WS API v2: added support for atomic order updates (36d10c4) 229 | - OrderBook: added arrayOBMidPrice helper (f0e3074) 230 | - OrderBook: added checksum helpers (cab9635) 231 | - refactored general model handling (broke out field indexes) (c616696) 232 | - and many small fixes & tweaks 233 | 234 | 2.0.0-beta 235 | 236 | - WS API v2: added optional auto-reconnect 237 | - WS API v2: added optional packet watchdog 238 | - WS API v2: added optional order packet buffering via multi-op endpoint 239 | - WS API v2: added optional order book & candle managment/persistence 240 | - WS API v2: added optional seq number verification/audit 241 | - WS API v2: added many extra callback/listener funcs (i.e. onMaintenanceStart) 242 | - WS API v2: added ability to mass-unsubscribe listeners by group ID 243 | - WS API v2: most callback methods now support message filtering 244 | - WS API v2: replaced transform logic w/ model classes (i.e. Order) 245 | - WS API v2: many methods now return promises, such as submitOrder() 246 | - REST API v2: transform method updated to match WSv2 class 247 | - REST API v1: minor refactor, methods unchanged 248 | - REST API v2: minor refactor, methods unchanged 249 | - WS API v1: minor refactor, methods unchanged 250 | - BFX constructor exposes & caches clients on `.rest()` and `.ws()` methods 251 | - Updated ws2 examples 252 | - Added model classes (OrderBook, Order, Trade, etc) 253 | 254 | 1.2.1 255 | 256 | - REST API v2: use /candles/ endpoint for candles data 257 | - WS API v2: Candles event provides key 258 | - Improve error message for nonce errors 259 | - Examples: added example for WS2 orders 260 | 261 | 1.2.0 262 | 263 | - REST API v1: Added support for `/orders/hist` endpoint 264 | - REST API v2: Added support for `auth/r/trades/{symbol}/hist` endpoint 265 | - WS API v2: Candles supports now `key` to identify subscription 266 | - REST API v1: Fix `claim_position` argument handling 267 | -------------------------------------------------------------------------------- /docs/util_ws2.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | util/ws2.js - Documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 35 | 36 |
37 | 38 |

util/ws2.js

39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
48 |
'use strict'
49 | 
50 | const _findLast = require('lodash/findLast')
51 | 
52 | /**
53 |  * Resolves the message payload; useful for getting around sequence numbers
54 |  *
55 |  * @param {Array} msg - message to parse
56 |  * @returns {Array} payload - undefined if not found
57 |  */
58 | module.exports = (msg = []) => {
59 |   return _findLast(msg, i => Array.isArray(i))
60 | }
61 | 
62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | 72 |
73 | 74 |
75 | Documentation generated by JSDoc 4.0.3 on Tue Sep 17 2024 00:59:21 GMT+0200 (Central European Summer Time) using the docdash theme. 76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /docs/scripts/prettify/Apache-License-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /docs/util_precision.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | util/precision.js - Documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 35 | 36 |
37 | 38 |

util/precision.js

39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
48 |
const Big = require('bignumber.js')
 49 | 
 50 | const DEFAULT_SIG_FIGS = 5
 51 | const PRICE_SIG_FIGS = 5
 52 | const AMOUNT_DECIMALS = 8
 53 | 
 54 | /**
 55 |  * Smartly set the precision (decimal) on a value based off of the significant
 56 |  * digit maximum. For example, calling with 3.34 when the max sig figs allowed
 57 |  * is 5 would return '3.3400', the representation number of decimals IF they
 58 |  * weren't zeros.
 59 |  *
 60 |  * @param {number} number - number to manipulate
 61 |  * @param {number} [maxSigs] - default 5
 62 |  * @returns {string} str
 63 |  */
 64 | const setSigFig = (number = 0, maxSigs = DEFAULT_SIG_FIGS) => {
 65 |   const n = +(number)
 66 |   if (!isFinite(n)) {
 67 |     return number
 68 |   }
 69 |   const value = n.toPrecision(maxSigs)
 70 | 
 71 |   return /e/.test(value)
 72 |     ? new Big(value).toString()
 73 |     : value
 74 | }
 75 | 
 76 | const setPrecision = (number = 0, decimals = 0) => {
 77 |   const n = +(number)
 78 | 
 79 |   return (isFinite(n))
 80 |     ? n.toFixed(decimals)
 81 |     : number
 82 | }
 83 | 
 84 | const prepareAmount = (amount = 0) => {
 85 |   return setPrecision(amount, AMOUNT_DECIMALS)
 86 | }
 87 | 
 88 | const preparePrice = (price = 0) => {
 89 |   return setSigFig(price, PRICE_SIG_FIGS)
 90 | }
 91 | 
 92 | module.exports = {
 93 |   setSigFig, setPrecision, prepareAmount, preparePrice
 94 | }
 95 | 
96 |
97 |
98 | 99 | 100 | 101 | 102 | 103 | 104 |
105 | 106 |
107 | 108 |
109 | Documentation generated by JSDoc 4.0.3 on Tue Sep 17 2024 00:59:21 GMT+0200 (Central European Summer Time) using the docdash theme. 110 |
111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /docs/scripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p ul { 280 | padding: 0 10px; 281 | } 282 | 283 | nav > ul > li > a { 284 | color: #606; 285 | margin-top: 10px; 286 | } 287 | 288 | nav ul ul a { 289 | color: hsl(207, 1%, 60%); 290 | border-left: 1px solid hsl(207, 10%, 86%); 291 | } 292 | 293 | nav ul ul a, 294 | nav ul ul a:active { 295 | padding-left: 20px 296 | } 297 | 298 | nav h2 { 299 | font-size: 13px; 300 | margin: 10px 0 0 0; 301 | padding: 0; 302 | } 303 | 304 | nav > h2 > a { 305 | margin: 10px 0 -10px; 306 | color: #606 !important; 307 | } 308 | 309 | footer { 310 | color: hsl(0, 0%, 28%); 311 | margin-left: 250px; 312 | display: block; 313 | padding: 15px; 314 | font-style: italic; 315 | font-size: 90%; 316 | } 317 | 318 | .ancestors { 319 | color: #999 320 | } 321 | 322 | .ancestors a { 323 | color: #999 !important; 324 | } 325 | 326 | .clear { 327 | clear: both 328 | } 329 | 330 | .important { 331 | font-weight: bold; 332 | color: #950B02; 333 | } 334 | 335 | .yes-def { 336 | text-indent: -1000px 337 | } 338 | 339 | .type-signature { 340 | color: #CA79CA 341 | } 342 | 343 | .type-signature:last-child { 344 | color: #eee; 345 | } 346 | 347 | .name, .signature { 348 | font-family: Consolas, Monaco, 'Andale Mono', monospace 349 | } 350 | 351 | .signature { 352 | color: #fc83ff; 353 | } 354 | 355 | .details { 356 | margin-top: 6px; 357 | border-left: 2px solid #DDD; 358 | line-height: 20px; 359 | font-size: 14px; 360 | } 361 | 362 | .details dt { 363 | width: auto; 364 | float: left; 365 | padding-left: 10px; 366 | } 367 | 368 | .details dd { 369 | margin-left: 70px; 370 | margin-top: 6px; 371 | margin-bottom: 6px; 372 | } 373 | 374 | .details ul { 375 | margin: 0 376 | } 377 | 378 | .details ul { 379 | list-style-type: none 380 | } 381 | 382 | .details pre.prettyprint { 383 | margin: 0 384 | } 385 | 386 | .details .object-value { 387 | padding-top: 0 388 | } 389 | 390 | .description { 391 | margin-bottom: 1em; 392 | margin-top: 1em; 393 | } 394 | 395 | .code-caption { 396 | font-style: italic; 397 | font-size: 107%; 398 | margin: 0; 399 | } 400 | 401 | .prettyprint { 402 | font-size: 14px; 403 | overflow: auto; 404 | } 405 | 406 | .prettyprint.source { 407 | width: inherit; 408 | line-height: 18px; 409 | display: block; 410 | background-color: #0d152a; 411 | color: #aeaeae; 412 | } 413 | 414 | .prettyprint code { 415 | line-height: 18px; 416 | display: block; 417 | background-color: #0d152a; 418 | color: #4D4E53; 419 | } 420 | 421 | .prettyprint > code { 422 | padding: 15px; 423 | } 424 | 425 | .prettyprint .linenums code { 426 | padding: 0 15px 427 | } 428 | 429 | .prettyprint .linenums li:first-of-type code { 430 | padding-top: 15px 431 | } 432 | 433 | .prettyprint code span.line { 434 | display: inline-block 435 | } 436 | 437 | .prettyprint.linenums { 438 | padding-left: 70px; 439 | -webkit-user-select: none; 440 | -moz-user-select: none; 441 | -ms-user-select: none; 442 | user-select: none; 443 | } 444 | 445 | .prettyprint.linenums ol { 446 | padding-left: 0 447 | } 448 | 449 | .prettyprint.linenums li { 450 | border-left: 3px #34446B solid; 451 | } 452 | 453 | .prettyprint.linenums li.selected, .prettyprint.linenums li.selected * { 454 | background-color: #34446B; 455 | } 456 | 457 | .prettyprint.linenums li * { 458 | -webkit-user-select: text; 459 | -moz-user-select: text; 460 | -ms-user-select: text; 461 | user-select: text; 462 | } 463 | 464 | .prettyprint.linenums li code:empty:after { 465 | content:""; 466 | display:inline-block; 467 | width:0px; 468 | } 469 | 470 | table { 471 | border-spacing: 0; 472 | border: 1px solid #ddd; 473 | border-collapse: collapse; 474 | border-radius: 3px; 475 | box-shadow: 0 1px 3px rgba(0,0,0,0.1); 476 | width: 100%; 477 | font-size: 14px; 478 | margin: 1em 0; 479 | } 480 | 481 | td, th { 482 | margin: 0px; 483 | text-align: left; 484 | vertical-align: top; 485 | padding: 10px; 486 | display: table-cell; 487 | } 488 | 489 | thead tr, thead tr { 490 | background-color: #fff; 491 | font-weight: bold; 492 | border-bottom: 1px solid #ddd; 493 | } 494 | 495 | .params .type { 496 | white-space: nowrap; 497 | } 498 | 499 | .params code { 500 | white-space: pre; 501 | } 502 | 503 | .params td, .params .name, .props .name, .name code { 504 | color: #4D4E53; 505 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 506 | font-size: 100%; 507 | } 508 | 509 | .params td { 510 | border-top: 1px solid #eee 511 | } 512 | 513 | .params td.description > p:first-child, .props td.description > p:first-child { 514 | margin-top: 0; 515 | padding-top: 0; 516 | } 517 | 518 | .params td.description > p:last-child, .props td.description > p:last-child { 519 | margin-bottom: 0; 520 | padding-bottom: 0; 521 | } 522 | 523 | span.param-type, .params td .param-type, .param-type dd { 524 | color: #606; 525 | font-family: Consolas, Monaco, 'Andale Mono', monospace 526 | } 527 | 528 | .param-type dt, .param-type dd { 529 | display: inline-block 530 | } 531 | 532 | .param-type { 533 | margin: 14px 0; 534 | } 535 | 536 | .disabled { 537 | color: #454545 538 | } 539 | 540 | /* navicon button */ 541 | .navicon-button { 542 | display: none; 543 | position: relative; 544 | padding: 2.0625rem 1.5rem; 545 | transition: 0.25s; 546 | cursor: pointer; 547 | -webkit-user-select: none; 548 | -moz-user-select: none; 549 | -ms-user-select: none; 550 | user-select: none; 551 | opacity: .8; 552 | } 553 | .navicon-button .navicon:before, .navicon-button .navicon:after { 554 | transition: 0.25s; 555 | } 556 | .navicon-button:hover { 557 | transition: 0.5s; 558 | opacity: 1; 559 | } 560 | .navicon-button:hover .navicon:before, .navicon-button:hover .navicon:after { 561 | transition: 0.25s; 562 | } 563 | .navicon-button:hover .navicon:before { 564 | top: .825rem; 565 | } 566 | .navicon-button:hover .navicon:after { 567 | top: -.825rem; 568 | } 569 | 570 | /* navicon */ 571 | .navicon { 572 | position: relative; 573 | width: 2.5em; 574 | height: .3125rem; 575 | background: #000; 576 | transition: 0.3s; 577 | border-radius: 2.5rem; 578 | } 579 | .navicon:before, .navicon:after { 580 | display: block; 581 | content: ""; 582 | height: .3125rem; 583 | width: 2.5rem; 584 | background: #000; 585 | position: absolute; 586 | z-index: -1; 587 | transition: 0.3s 0.25s; 588 | border-radius: 1rem; 589 | } 590 | .navicon:before { 591 | top: .625rem; 592 | } 593 | .navicon:after { 594 | top: -.625rem; 595 | } 596 | 597 | /* open */ 598 | .nav-trigger:checked + label:not(.steps) .navicon:before, 599 | .nav-trigger:checked + label:not(.steps) .navicon:after { 600 | top: 0 !important; 601 | } 602 | 603 | .nav-trigger:checked + label .navicon:before, 604 | .nav-trigger:checked + label .navicon:after { 605 | transition: 0.5s; 606 | } 607 | 608 | /* Minus */ 609 | .nav-trigger:checked + label { 610 | -webkit-transform: scale(0.75); 611 | transform: scale(0.75); 612 | } 613 | 614 | /* × and + */ 615 | .nav-trigger:checked + label.plus .navicon, 616 | .nav-trigger:checked + label.x .navicon { 617 | background: transparent; 618 | } 619 | 620 | .nav-trigger:checked + label.plus .navicon:before, 621 | .nav-trigger:checked + label.x .navicon:before { 622 | -webkit-transform: rotate(-45deg); 623 | transform: rotate(-45deg); 624 | background: #FFF; 625 | } 626 | 627 | .nav-trigger:checked + label.plus .navicon:after, 628 | .nav-trigger:checked + label.x .navicon:after { 629 | -webkit-transform: rotate(45deg); 630 | transform: rotate(45deg); 631 | background: #FFF; 632 | } 633 | 634 | .nav-trigger:checked + label.plus { 635 | -webkit-transform: scale(0.75) rotate(45deg); 636 | transform: scale(0.75) rotate(45deg); 637 | } 638 | 639 | .nav-trigger:checked ~ nav { 640 | left: 0 !important; 641 | } 642 | 643 | .nav-trigger:checked ~ .overlay { 644 | display: block; 645 | } 646 | 647 | .nav-trigger { 648 | position: fixed; 649 | top: 0; 650 | clip: rect(0, 0, 0, 0); 651 | } 652 | 653 | .overlay { 654 | display: none; 655 | position: fixed; 656 | top: 0; 657 | bottom: 0; 658 | left: 0; 659 | right: 0; 660 | width: 100%; 661 | height: 100%; 662 | background: hsla(0, 0%, 0%, 0.5); 663 | z-index: 1; 664 | } 665 | 666 | /* nav level */ 667 | .level-hide { 668 | display: none; 669 | } 670 | html[data-search-mode] .level-hide { 671 | display: block; 672 | } 673 | 674 | 675 | @media only screen and (max-width: 680px) { 676 | body { 677 | overflow-x: hidden; 678 | } 679 | 680 | nav { 681 | background: #FFF; 682 | width: 250px; 683 | height: 100%; 684 | position: fixed; 685 | top: 0; 686 | right: 0; 687 | bottom: 0; 688 | left: -250px; 689 | z-index: 3; 690 | padding: 0 10px; 691 | transition: left 0.2s; 692 | } 693 | 694 | .navicon-button { 695 | display: inline-block; 696 | position: fixed; 697 | top: 1.5em; 698 | right: 0; 699 | z-index: 2; 700 | } 701 | 702 | #main { 703 | width: 100%; 704 | } 705 | 706 | #main h1.page-title { 707 | margin: 1em 0; 708 | } 709 | 710 | #main section { 711 | padding: 0; 712 | } 713 | 714 | footer { 715 | margin-left: 0; 716 | } 717 | } 718 | 719 | /** Add a '#' to static members */ 720 | [data-type="member"] a::before { 721 | content: '#'; 722 | display: inline-block; 723 | margin-left: -14px; 724 | margin-right: 5px; 725 | } 726 | 727 | #disqus_thread{ 728 | margin-left: 30px; 729 | } 730 | 731 | @font-face { 732 | font-family: 'Montserrat'; 733 | font-style: normal; 734 | font-weight: 400; 735 | src: url('../fonts/Montserrat/Montserrat-Regular.eot'); /* IE9 Compat Modes */ 736 | src: url('../fonts/Montserrat/Montserrat-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 737 | url('../fonts/Montserrat/Montserrat-Regular.woff2') format('woff2'), /* Super Modern Browsers */ 738 | url('../fonts/Montserrat/Montserrat-Regular.woff') format('woff'), /* Pretty Modern Browsers */ 739 | url('../fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); /* Safari, Android, iOS */ 740 | } 741 | 742 | @font-face { 743 | font-family: 'Montserrat'; 744 | font-style: normal; 745 | font-weight: 700; 746 | src: url('../fonts/Montserrat/Montserrat-Bold.eot'); /* IE9 Compat Modes */ 747 | src: url('../fonts/Montserrat/Montserrat-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ 748 | url('../fonts/Montserrat/Montserrat-Bold.woff2') format('woff2'), /* Super Modern Browsers */ 749 | url('../fonts/Montserrat/Montserrat-Bold.woff') format('woff'), /* Pretty Modern Browsers */ 750 | url('../fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); /* Safari, Android, iOS */ 751 | } 752 | 753 | @font-face { 754 | font-family: 'Source Sans Pro'; 755 | src: url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot'); 756 | src: url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot?#iefix') format('embedded-opentype'), 757 | url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2') format('woff2'), 758 | url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff') format('woff'), 759 | url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf') format('truetype'), 760 | url('../fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg#source_sans_proregular') format('svg'); 761 | font-weight: 400; 762 | font-style: normal; 763 | } 764 | 765 | @font-face { 766 | font-family: 'Source Sans Pro'; 767 | src: url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot'); 768 | src: url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot?#iefix') format('embedded-opentype'), 769 | url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2') format('woff2'), 770 | url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff') format('woff'), 771 | url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf') format('truetype'), 772 | url('../fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg#source_sans_prolight') format('svg'); 773 | font-weight: 300; 774 | font-style: normal; 775 | 776 | } 777 | --------------------------------------------------------------------------------